From 5e4e6a711d1a943d02d3417ff8c83c4ccf0205aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 27 May 2024 12:43:49 +0200 Subject: [PATCH] RDART-930: Refactor handles (#1550) * Refactor handles subscription.dart * Refactor: use import over part for handle_base.dart * Fix regression: Check if close on handle deref * TMP: Skip some tests (something broke with RootedHandle) * Ups! * Wip * After rebase * wip * Fix after rebase * Revert "TMP: Skip some tests (something broke with RootedHandle)" This reverts commit 50812177a35ba3aff64860af791f6fab9e7b897d. * Use dart_test.yaml to configure tags. Make baas a tag instead of a prefix. Simplify * Fix nullPtr related bug * Doh! I'll go die in shame!! * Refactor RealmHandle * Refactor ConfigHandle * Refactor _RealmQueryHandler (now QueryHandle) * Refactor RealmObjectHandle (now ObjectHandle) * Refactor RealmResultHandle (now ResultsHandle) * More RealmHandle stuff * Don't need Tuple * Expose disableAutoRefreshForTesting * Refactor UserHandle * RealmHandle.findAll * Realm.find/.findExiting/.renameProperty * Drop superfluous this. * Refactor AppHandle * Refactor SchedulerHandle * Move config handles * Refactor SessionHandle * UserHandle.linkCredentials/.createApiKey/.fetchApiKey/.fetchAllApiKeys.deleteApiKey/.disableApiKey/.enableApiKey * Refactor RealmListHandle (now ListHandle) * Refactor RealmSetHandle (now SetHandle) * Refactor RealmAppCredentialsHandle (now CredentialsHandle) * WIP * WIP2 * Organize imports * ResultsHandle stuff * More UserHandle stuff * subscribeForSchemaNotification (something rubs me the wrong way about this) * Move MapHandle.query * More ObjectHandle stuff * Move callback functions into handle files * Move resolveX to XHandle.resolveIn * Make a bunch of function public in prep for getting rid of parts * Refactor XChangesHandle * Use CredentialsHandle not Credentils * Traffic in ResultsHandle not RealmResult + dart fix stuff * Split part files * Move XTokenHandles * toNative extension method replaces toRealmValue function * Replace last use of deprecated Pointer.elementAt(i) * More RealmHandle stuff * Replace invokeGetBool and invokeGetPointer * Run melos custom_format * Move callAppFunction stuff * Refactor equals * Move raiseIfNull to HandleBase ctor. Rename to be explicit about call to getLastError * Completely unrelated spelling corrections in CHANGELOG * Seperate AsyncOpenTaskHandle and NotificationHandle from realm_core.dart * moving guardSynchronousCallback and getApp out of realm_core.dart * Split native convertion functions into separate files * Last leg.. for now * PR feedback * Fix rebase * Tweat memEquals perf test * fixup! Last leg.. for now * Fix after rebase * more PR feedback --- .vscode/settings.json | 1 + CHANGELOG.md | 14 +- analysis_options.yaml | 1 - packages/realm/bin/realm.dart | 2 +- .../lib/src/realm_common_base.dart | 8 - .../realm_common/lib/src/realm_types.dart | 10 +- packages/realm_dart/bin/realm_dart.dart | 2 +- packages/realm_dart/dart_test.yaml | 2 + packages/realm_dart/lib/src/app.dart | 29 +- .../lib/src/cli/common/archive.dart | 1 - .../realm_dart/lib/src/cli/common/utils.dart | 2 +- packages/realm_dart/lib/src/collections.dart | 8 +- .../realm_dart/lib/src/configuration.dart | 15 +- packages/realm_dart/lib/src/credentials.dart | 41 +- packages/realm_dart/lib/src/list.dart | 53 +- packages/realm_dart/lib/src/map.dart | 60 +- packages/realm_dart/lib/src/migration.dart | 10 +- .../realm_dart/lib/src/native/app_handle.dart | 427 ++ .../src/native/async_open_task_handle.dart | 80 + .../src/native/collection_changes_handle.dart | 78 + .../src/native/collection_handle_base.dart | 51 + .../lib/src/native/config_handle.dart | 268 ++ .../realm_dart/lib/src/native/convert.dart | 24 + .../lib/src/native/convert_native.dart | 2 + .../lib/src/native/credentials_handle.dart | 82 + .../realm_dart/lib/src/native/decimal128.dart | 40 +- .../lib/src/native/error_handling.dart | 69 + packages/realm_dart/lib/src/native/ffi.dart | 4 + .../lib/src/native/from_native.dart | 358 ++ .../lib/src/native/handle_base.dart | 93 + .../lib/src/native/http_transport_handle.dart | 153 + .../lib/src/native/list_handle.dart | 146 + .../lib/src/native/map_changes_handle.dart | 55 + .../realm_dart/lib/src/native/map_handle.dart | 198 + .../mutable_subscription_set_handle.dart | 90 + .../src/native/notification_token_handle.dart | 35 + .../lib/src/native/object_handle.dart | 206 + .../lib/src/native/query_handle.dart | 22 + .../realm_dart/lib/src/native/realm_core.dart | 3890 +---------------- .../lib/src/native/realm_handle.dart | 452 ++ .../lib/src/native/realm_library.dart | 24 + .../lib/src/native/results_handle.dart | 117 + .../lib/src/native/rooted_handle.dart | 41 + .../lib/src/native/scheduler_handle.dart | 22 + .../lib/src/native/schema_handle.dart | 90 + .../lib/src/native/session_handle.dart | 164 + .../realm_dart/lib/src/native/set_handle.dart | 131 + .../lib/src/native/subscription_handle.dart | 28 + .../src/native/subscription_set_handle.dart | 81 + .../realm_dart/lib/src/native/to_native.dart | 173 + .../lib/src/native/user_handle.dart | 354 ++ packages/realm_dart/lib/src/realm_class.dart | 160 +- packages/realm_dart/lib/src/realm_object.dart | 78 +- packages/realm_dart/lib/src/results.dart | 34 +- packages/realm_dart/lib/src/scheduler.dart | 8 +- packages/realm_dart/lib/src/session.dart | 34 +- packages/realm_dart/lib/src/set.dart | 41 +- packages/realm_dart/lib/src/subscription.dart | 112 +- packages/realm_dart/lib/src/user.dart | 47 +- packages/realm_dart/test/app_test.dart | 2 +- packages/realm_dart/test/baas_helper.dart | 4 +- .../realm_dart/test/configuration_test.dart | 3 +- packages/realm_dart/test/decimal128_test.dart | 1 + .../realm_dart/test/dynamic_realm_test.dart | 2 - packages/realm_dart/test/embedded_test.dart | 1 + packages/realm_dart/test/realm_test.dart | 7 +- .../realm_dart/test/realm_value_test.dart | 5 +- packages/realm_dart/test/test.dart | 39 +- .../good_test_data/const_initializer.dart | 2 +- 69 files changed, 4642 insertions(+), 4245 deletions(-) create mode 100644 packages/realm_dart/dart_test.yaml create mode 100644 packages/realm_dart/lib/src/native/app_handle.dart create mode 100644 packages/realm_dart/lib/src/native/async_open_task_handle.dart create mode 100644 packages/realm_dart/lib/src/native/collection_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/native/collection_handle_base.dart create mode 100644 packages/realm_dart/lib/src/native/config_handle.dart create mode 100644 packages/realm_dart/lib/src/native/convert.dart create mode 100644 packages/realm_dart/lib/src/native/convert_native.dart create mode 100644 packages/realm_dart/lib/src/native/credentials_handle.dart create mode 100644 packages/realm_dart/lib/src/native/error_handling.dart create mode 100644 packages/realm_dart/lib/src/native/ffi.dart create mode 100644 packages/realm_dart/lib/src/native/from_native.dart create mode 100644 packages/realm_dart/lib/src/native/handle_base.dart create mode 100644 packages/realm_dart/lib/src/native/http_transport_handle.dart create mode 100644 packages/realm_dart/lib/src/native/list_handle.dart create mode 100644 packages/realm_dart/lib/src/native/map_changes_handle.dart create mode 100644 packages/realm_dart/lib/src/native/map_handle.dart create mode 100644 packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart create mode 100644 packages/realm_dart/lib/src/native/notification_token_handle.dart create mode 100644 packages/realm_dart/lib/src/native/object_handle.dart create mode 100644 packages/realm_dart/lib/src/native/query_handle.dart create mode 100644 packages/realm_dart/lib/src/native/realm_handle.dart create mode 100644 packages/realm_dart/lib/src/native/realm_library.dart create mode 100644 packages/realm_dart/lib/src/native/results_handle.dart create mode 100644 packages/realm_dart/lib/src/native/rooted_handle.dart create mode 100644 packages/realm_dart/lib/src/native/scheduler_handle.dart create mode 100644 packages/realm_dart/lib/src/native/schema_handle.dart create mode 100644 packages/realm_dart/lib/src/native/session_handle.dart create mode 100644 packages/realm_dart/lib/src/native/set_handle.dart create mode 100644 packages/realm_dart/lib/src/native/subscription_handle.dart create mode 100644 packages/realm_dart/lib/src/native/subscription_set_handle.dart create mode 100644 packages/realm_dart/lib/src/native/to_native.dart create mode 100644 packages/realm_dart/lib/src/native/user_handle.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 2411f1d50..483d755b2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -25,6 +25,7 @@ "geospatial", "HRESULT", "keepalive", + "keypaths", "loggable", "maccatalyst", "mugaritz", diff --git a/CHANGELOG.md b/CHANGELOG.md index 8986c3bbe..922c56f04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -82,7 +82,7 @@ * Fixed a `DecryptionFailed` exception thrown when opening a small (<4k of data) Realm generated on a device with a page size of 4k if it was bundled and opened on a device with a larger page size. * Fixed an issue during a subsequent open of an encrypted Realm for some rare allocation patterns when the top ref was within ~50 bytes of the end of a page. This could manifest as a DecryptionFailed exception or as an assertion: `encrypted_file_mapping.hpp:183: Assertion failed: local_ndx < m_page_state.size()`. * Schema initialization could hit an assertion failure if the sync client applied a downloaded changeset while the Realm file was in the process of being opened. (Core 14.6.0) -* Improve perfomance of "chained OR equality" queries for UUID/ObjectId types and RQL parsed "IN" queries on string/int/uuid/objectid types. (Core 14.6.0) +* Improve performance of "chained OR equality" queries for UUID/ObjectId types and RQL parsed "IN" queries on string/int/uuid/objectid types. (Core 14.6.0) * Fixed a bug when running a IN query (or a query of the pattern `x == 1 OR x == 2 OR x == 3`) when evaluating on a string property with an empty string in the search condition. Matches with an empty string would have been evaluated as if searching for a null string instead. (Core 14.6.2) ### Compatibility @@ -184,9 +184,9 @@ ``` * Removed `SchemaObject.properties` - instead, `SchemaObject` is now an iterable collection of `Property`. (Issue [#1449](https://github.com/realm/realm-dart/issues/1449)) * `SyncProgress.transferredBytes` and `SyncProgress.transferableBytes` have been consolidated into `SyncProgress.progressEstimate`. The values reported previously were incorrect and did not accurately represent bytes either. The new field better conveys the uncertainty around the progress being reported. With this release, we're reporting accurate estimates for upload progress, but estimating downloads is still unreliable. A future server and SDK release will add better estimations for download progress. (Issue [#1562](https://github.com/realm/realm-dart/issues/1562)) -* `Realm.logger` is no longer settable, and no longer implements `Logger` from package `logging`. In particular you can no longer call `Realm.logger.level =`. Instead you should call `Realm.logger.setLogLevel(RealmLogLevel level, {RealmLogCategory? category})` that takes an optional category. If no category is exlicitly given, then `RealmLogCategory.realm` is assumed. +* `Realm.logger` is no longer settable, and no longer implements `Logger` from package `logging`. In particular you can no longer call `Realm.logger.level =`. Instead you should call `Realm.logger.setLogLevel(RealmLogLevel level, {RealmLogCategory? category})` that takes an optional category. If no category is explicitly given, then `RealmLogCategory.realm` is assumed. - Also, note that setting a level is no longer local to the current isolate, but shared accross all isolates. At the core level there is just one process wide logger. + Also, note that setting a level is no longer local to the current isolate, but shared across all isolates. At the core level there is just one process wide logger. Categories form a hierarchy and setting the log level of a parent category will override the level of its children. The hierarchy is exposed in a type safe manner with: ```dart @@ -928,7 +928,7 @@ class _Address { * Queries on results didn't filter the existing results. ([#908](https://github.com/realm/realm-dart/issues/908)). Example ```dart - expect(realm.query('FALSEPREDICATE').query('TRUEPREDICATE'), isEmpty); //<-- Fails if a Persion object exists + expect(realm.query('FALSEPREDICATE').query('TRUEPREDICATE'), isEmpty); //<-- Fails if a Person object exists ``` * Fixed copying of native structs for session errors and http requests. ([#924](https://github.com/realm/realm-dart/pull/924)) * Fixed a crash when closing the SyncSession on App instance teardown. ([#5752](https://github.com/realm/realm-core/issues/5752)) @@ -1091,7 +1091,7 @@ class _Address { ```dart final subscription = realm.all().changes.listen((changes) { - changes.inserted // indexes of inserted ojbects + changes.inserted // indexes of inserted objects changes.modified // indexes of modified objects changes.deleted // indexes of deleted objects changes.newModified // indexes of modified objects after deletions and insertions are accounted for. @@ -1231,7 +1231,7 @@ Notes: This release is a prerelease version. All API's might change without warn Notes: This release is a prerelease version. All API's might change without warning and no guarantees are given about stability. ### Enhancements -* Completеly rewritten from the ground up with sound null safety and using Dart FFI +* Completely rewritten from the ground up with sound null safety and using Dart FFI ### Fixed * Realm close stops internal scheduler. @@ -1247,7 +1247,7 @@ Notes: This release is a prerelease version. All API's might change without warn Notes: This release is a prerelease version. All API's might change without warning and no guarantees are given about stability. ### Enhancements -* Completеly rewritten from the ground up with sound null safety and using Dart FFI +* Completely rewritten from the ground up with sound null safety and using Dart FFI ### Compatibility * Dart ^2.15 on Windows, MacOS and Linux diff --git a/analysis_options.yaml b/analysis_options.yaml index dc3d7cd3d..16bcf0407 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -19,7 +19,6 @@ linter: rules: avoid_relative_lib_imports: false package_api_docs: true - dangling_library_doc_comments: false # For more information about the core and recommended set of lints, see # https://dart.dev/go/core-lints diff --git a/packages/realm/bin/realm.dart b/packages/realm/bin/realm.dart index 80569db28..a8e88e123 100644 --- a/packages/realm/bin/realm.dart +++ b/packages/realm/bin/realm.dart @@ -1,6 +1,6 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 - import 'package:realm_dart/src/cli/main.dart' as x; + void main(List arguments) => x.main(arguments); diff --git a/packages/realm_common/lib/src/realm_common_base.dart b/packages/realm_common/lib/src/realm_common_base.dart index e89b97b27..742085801 100644 --- a/packages/realm_common/lib/src/realm_common_base.dart +++ b/packages/realm_common/lib/src/realm_common_base.dart @@ -130,11 +130,3 @@ class Backlink { final Symbol fieldName; const Backlink(this.fieldName); } - -/// @nodoc -class Tuple { - T1 item1; - T2 item2; - - Tuple(this.item1, this.item2); -} diff --git a/packages/realm_common/lib/src/realm_types.dart b/packages/realm_common/lib/src/realm_types.dart index 48ab76ebf..af031ae7e 100644 --- a/packages/realm_common/lib/src/realm_types.dart +++ b/packages/realm_common/lib/src/realm_types.dart @@ -86,11 +86,11 @@ enum RealmCollectionType { map; String get plural => switch (this) { - RealmCollectionType.list => 'lists', - RealmCollectionType.set => 'sets', - RealmCollectionType.map => 'maps', - _ => 'none' - }; + RealmCollectionType.list => 'lists', + RealmCollectionType.set => 'sets', + RealmCollectionType.map => 'maps', + _ => 'none', + }; } /// A base class of all Realm errors. diff --git a/packages/realm_dart/bin/realm_dart.dart b/packages/realm_dart/bin/realm_dart.dart index 80569db28..a8e88e123 100644 --- a/packages/realm_dart/bin/realm_dart.dart +++ b/packages/realm_dart/bin/realm_dart.dart @@ -1,6 +1,6 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 - import 'package:realm_dart/src/cli/main.dart' as x; + void main(List arguments) => x.main(arguments); diff --git a/packages/realm_dart/dart_test.yaml b/packages/realm_dart/dart_test.yaml new file mode 100644 index 000000000..8e7275b44 --- /dev/null +++ b/packages/realm_dart/dart_test.yaml @@ -0,0 +1,2 @@ +tags: + baas: { timeout: 2x } \ No newline at end of file diff --git a/packages/realm_dart/lib/src/app.dart b/packages/realm_dart/lib/src/app.dart index b0025e7a2..3e695a645 100644 --- a/packages/realm_dart/lib/src/app.dart +++ b/packages/realm_dart/lib/src/app.dart @@ -7,11 +7,12 @@ import 'dart:io'; import 'dart:isolate'; import 'package:meta/meta.dart'; -import 'package:path/path.dart' as _path; +import 'package:path/path.dart' as path; import '../realm.dart'; import 'credentials.dart'; import 'logging.dart'; +import 'native/app_handle.dart'; import 'native/realm_core.dart'; import 'user.dart'; @@ -125,7 +126,7 @@ class AppConfiguration { this.maxConnectionTimeout = const Duration(minutes: 2), HttpClient? httpClient, }) : baseUrl = baseUrl ?? Uri.parse(realmCore.getDefaultBaseUrl()), - baseFilePath = baseFilePath ?? Directory(_path.dirname(Configuration.defaultRealmPath)), + baseFilePath = baseFilePath ?? Directory(path.dirname(Configuration.defaultRealmPath)), httpClient = httpClient ?? _defaultClient { if (appId == '') { throw RealmException('Supplied appId must be a non-empty value'); @@ -144,7 +145,7 @@ class App implements Finalizable { /// The id of this application. This is the same as the appId in the [AppConfiguration] used to /// create this [App]. - String get id => realmCore.appGetId(this); + String get id => handle.id; /// Create an app with a particular [AppConfiguration]. This constructor should only be used on the main isolate and, /// ideally, only once as soon as the app starts. @@ -163,7 +164,7 @@ class App implements Finalizable { /// on the main isolate. If an App hasn't been already constructed with the same id, will return null. This method is safe to call /// on a background isolate. static App? getById(String id, {Uri? baseUrl}) { - final handle = realmCore.getApp(id, baseUrl?.toString()); + final handle = AppHandle.get(id, baseUrl?.toString()); return handle == null ? null : App._(handle); } @@ -171,18 +172,18 @@ class App implements Finalizable { static AppHandle _createApp(AppConfiguration configuration) { configuration.baseFilePath.createSync(recursive: true); - return realmCore.createApp(configuration); + return AppHandle.from(configuration); } /// Logs in a user with the given credentials. Future logIn(Credentials credentials) async { - var userHandle = await realmCore.logIn(this, credentials); + var userHandle = await handle.logIn(credentials.handle); return UserInternal.create(userHandle, this); } /// Gets the currently logged in [User]. If none exists, `null` is returned. User? get currentUser { - final userHandle = realmCore.getCurrentUser(_handle); + final userHandle = _handle.currentUser; if (userHandle == null) { return null; } @@ -191,22 +192,22 @@ class App implements Finalizable { /// Gets all currently logged in users. Iterable get users { - return realmCore.getUsers(this).map((handle) => UserInternal.create(handle, this)); + return handle.users.map((handle) => UserInternal.create(handle, this)); } /// Removes a [user] and their local data from the device. If the user is logged in, they will be logged out in the process. Future removeUser(User user) async { - return await realmCore.removeUser(this, user); + return await handle.removeUser(user.handle); } /// Deletes a user and all its data from the device as well as the server. Future deleteUser(User user) async { - return await realmCore.deleteUser(this, user); + return await handle.deleteUser(user.handle); } /// Switches the [currentUser] to the one specified in [user]. void switchUser(User user) { - realmCore.switchUser(this, user); + handle.switchUser(user.handle); } /// Provide a hint to this app's sync client to reconnect. @@ -214,7 +215,7 @@ class App implements Finalizable { /// /// The sync client will always attempt to reconnect eventually, this is just a hint. void reconnect() { - realmCore.reconnect(this); + handle.reconnect(); } /// Returns the current value of the base URL used to communicate with the server. @@ -223,7 +224,7 @@ class App implements Finalizable { /// be updated with the new value until that operation has completed. @experimental Uri get baseUrl { - return Uri.parse(realmCore.getBaseUrl(this)); + return Uri.parse(handle.baseUrl); } /// Temporarily overrides the [baseUrl] value from [AppConfiguration] with a new [baseUrl] value @@ -237,7 +238,7 @@ class App implements Finalizable { /// The App will revert to using the value in [AppConfiguration] when it is restarted. @experimental Future updateBaseUrl(Uri? baseUrl) async { - return await realmCore.updateBaseUrl(this, baseUrl); + return await handle.updateBaseUrl(baseUrl); } /// Returns an instance of [EmailPasswordAuthProvider] diff --git a/packages/realm_dart/lib/src/cli/common/archive.dart b/packages/realm_dart/lib/src/cli/common/archive.dart index 23ab1dcbb..eabca8f4a 100644 --- a/packages/realm_dart/lib/src/cli/common/archive.dart +++ b/packages/realm_dart/lib/src/cli/common/archive.dart @@ -1,7 +1,6 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -/// import 'dart:io'; import 'package:tar/tar.dart'; import 'package:path/path.dart' as path; diff --git a/packages/realm_dart/lib/src/cli/common/utils.dart b/packages/realm_dart/lib/src/cli/common/utils.dart index c7d5c0851..78ac701f9 100644 --- a/packages/realm_dart/lib/src/cli/common/utils.dart +++ b/packages/realm_dart/lib/src/cli/common/utils.dart @@ -20,7 +20,7 @@ extension StringEx on String { bool isRealmCI = Platform.environment['REALM_CI'] != null; -FutureOr safe(FutureOr Function() f, {String message = 'Ignoring error', Function(Object e, StackTrace s)? onError}) async { +FutureOr safe(FutureOr Function() f, {String message = 'Ignoring error', void Function(Object e, StackTrace s)? onError}) async { try { return await f(); } catch (e, s) { diff --git a/packages/realm_dart/lib/src/collections.dart b/packages/realm_dart/lib/src/collections.dart index d41d450a0..71b25dfd0 100644 --- a/packages/realm_dart/lib/src/collections.dart +++ b/packages/realm_dart/lib/src/collections.dart @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:ffi'; -import 'native/realm_core.dart'; +import 'native/collection_changes_handle.dart'; /// Contains index information about objects that moved within the same collection. class Move { @@ -47,13 +47,11 @@ class MapChanges { /// Describes the changes in a Realm collection since the last time the notification callback was invoked. class RealmCollectionChanges implements Finalizable { - final RealmCollectionChangesHandle _handle; - CollectionChanges? _values; + final CollectionChangesHandle _handle; + late final CollectionChanges _changes = _handle.changes; RealmCollectionChanges(this._handle); - CollectionChanges get _changes => _values ??= realmCore.getCollectionChanges(_handle); - /// The indexes in the previous version of the collection which have been removed from this one. List get deleted => _changes.deletions; diff --git a/packages/realm_dart/lib/src/configuration.dart b/packages/realm_dart/lib/src/configuration.dart index 45998fcfe..44c11ce92 100644 --- a/packages/realm_dart/lib/src/configuration.dart +++ b/packages/realm_dart/lib/src/configuration.dart @@ -7,12 +7,17 @@ import 'dart:io'; // ignore: no_leading_underscores_for_library_prefixes import 'package:path/path.dart' as _path; + +import 'app.dart'; +import 'init.dart'; import 'logging.dart'; +import 'native/from_native.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; -import 'init.dart'; import 'user.dart'; +const encryptionKeySize = 64; + /// The signature of a callback used to determine if compaction /// should be attempted. /// @@ -225,8 +230,8 @@ abstract class Configuration implements Finalizable { return; } - if (key.length != realmCore.encryptionKeySize) { - throw RealmException("Wrong encryption key size (must be ${realmCore.encryptionKeySize}, but was ${key.length})"); + if (key.length != encryptionKeySize) { + throw RealmException("Wrong encryption key size (must be $encryptionKeySize, but was ${key.length})"); } int notAByteElement = key.firstWhere((e) => e > 255, orElse: () => -1); @@ -368,7 +373,7 @@ class FlexibleSyncConfiguration extends Configuration { }) : super._(); @override - String get _defaultPath => realmCore.getPathForUser(user); + String get _defaultPath => user.handle.path; } extension FlexibleSyncConfigurationInternal on FlexibleSyncConfiguration { @@ -653,7 +658,7 @@ class ClientResetError extends SyncError { throw RealmException("Missing `originalFilePath`"); } - return realmCore.immediatelyRunFileActions(_app!, originalFilePath!); + return _app.handle.resetRealm(originalFilePath!); } } diff --git a/packages/realm_dart/lib/src/credentials.dart b/packages/realm_dart/lib/src/credentials.dart index 21f2f20d8..14189c169 100644 --- a/packages/realm_dart/lib/src/credentials.dart +++ b/packages/realm_dart/lib/src/credentials.dart @@ -4,8 +4,9 @@ import 'dart:convert'; import 'dart:ffi'; -import 'native/realm_core.dart'; import 'app.dart'; +import 'native/convert.dart'; +import 'native/credentials_handle.dart'; import 'user.dart'; /// An enum containing all authentication providers. These have to be enabled manually for the application before they can be used. @@ -57,44 +58,44 @@ extension AuthProviderTypeInternal on AuthProviderType { /// A class, representing the credentials used for authenticating a [User] /// {@category Application} class Credentials implements Finalizable { - final RealmAppCredentialsHandle _handle; + final CredentialsHandle _handle; /// Returns a [Credentials] object that can be used to authenticate an anonymous user. /// Setting [reuseCredentials] to `false` will create a new anonymous user, upon [App.logIn]. /// [Anonymous Authentication Docs](https://www.mongodb.com/docs/atlas/app-services/authentication/anonymous/#anonymous-authentication) - Credentials.anonymous({bool reuseCredentials = true}) : _handle = realmCore.createAppCredentialsAnonymous(reuseCredentials); + Credentials.anonymous({bool reuseCredentials = true}) : _handle = CredentialsHandle.anonymous(reuseCredentials); /// Returns a [Credentials] object that can be used to authenticate a user with a Google account using an id token. - Credentials.apple(String idToken) : _handle = realmCore.createAppCredentialsApple(idToken); + Credentials.apple(String idToken) : _handle = CredentialsHandle.apple(idToken); /// Returns a [Credentials] object that can be used to authenticate a user with their email and password. /// A user can login with email and password only after they have registered their account and verified their /// email. /// [Email/Password Authentication Docs](https://www.mongodb.com/docs/atlas/app-services/authentication/email-password/#email-password-authentication) - Credentials.emailPassword(String email, String password) : _handle = realmCore.createAppCredentialsEmailPassword(email, password); + Credentials.emailPassword(String email, String password) : _handle = CredentialsHandle.emailPassword(email, password); /// Returns a [Credentials] object that can be used to authenticate a user with a custom JWT. /// [Custom-JWT Authentication Docs](https://www.mongodb.com/docs/atlas/app-services/authentication/custom-jwt/#custom-jwt-authentication) - Credentials.jwt(String token) : _handle = realmCore.createAppCredentialsJwt(token); + Credentials.jwt(String token) : _handle = CredentialsHandle.jwt(token); /// Returns a [Credentials] object that can be used to authenticate a user with a Facebook account. - Credentials.facebook(String accessToken) : _handle = realmCore.createAppCredentialsFacebook(accessToken); + Credentials.facebook(String accessToken) : _handle = CredentialsHandle.facebook(accessToken); /// Returns a [Credentials] object that can be used to authenticate a user with a Google account using an authentication code. - Credentials.googleAuthCode(String authCode) : _handle = realmCore.createAppCredentialsGoogleAuthCode(authCode); + Credentials.googleAuthCode(String authCode) : _handle = CredentialsHandle.googleAuthCode(authCode); /// Returns a [Credentials] object that can be used to authenticate a user with a Google account using an id token. - Credentials.googleIdToken(String idToken) : _handle = realmCore.createAppCredentialsGoogleIdToken(idToken); + Credentials.googleIdToken(String idToken) : _handle = CredentialsHandle.googleIdToken(idToken); /// Returns a [Credentials] object that can be used to authenticate a user with a custom Function. /// [Custom Function Authentication Docs](https://www.mongodb.com/docs/atlas/app-services/authentication/custom-function/) - Credentials.function(String payload) : _handle = realmCore.createAppCredentialsFunction(payload); + Credentials.function(String payload) : _handle = CredentialsHandle.function(payload); /// Returns a [Credentials] object that can be used to authenticate a user with an API key. /// To generate an API key, use [ApiKeyClient.create] or the App Services web UI. - Credentials.apiKey(String key) : _handle = realmCore.createAppCredentialsApiKey(key); + Credentials.apiKey(String key) : _handle = CredentialsHandle.apiKey(key); - AuthProviderType get provider => realmCore.userGetCredentialsProviderType(this); + AuthProviderType get provider => handle.providerType; } /// @nodoc @@ -104,7 +105,7 @@ extension CredentialsInternal on Credentials { _handle.keepAlive(); } - RealmAppCredentialsHandle get handle => _handle; + CredentialsHandle get handle => _handle; } /// A class, encapsulating functionality for users, logged in with [Credentials.emailPassword()]. @@ -123,38 +124,38 @@ class EmailPasswordAuthProvider implements Finalizable { /// /// Successful completion indicates that the user has been created on the server and can now be logged in with [Credentials.emailPassword()]. Future registerUser(String email, String password) async { - return realmCore.appEmailPasswordRegisterUser(app, email, password); + return app.handle.registerUser(email, password); } /// Confirms a user with the given token and token id. These are typically included in the registration email. Future confirmUser(String token, String tokenId) { - return realmCore.emailPasswordConfirmUser(app, token, tokenId); + return app.handle.confirmUser(token, tokenId); } /// Resend the confirmation email for a user to the given email. Future resendUserConfirmation(String email) { - return realmCore.emailPasswordResendUserConfirmation(app, email); + return app.handle.resendConfirmation(email); } /// Completes the reset password procedure by providing the desired new [password] using the /// password reset [token] and [tokenId] that were emailed to a user. Future completeResetPassword(String password, String token, String tokenId) { - return realmCore.emailPasswordCompleteResetPassword(app, password, token, tokenId); + return app.handle.completeResetPassword(password, token, tokenId); } /// Sends a password reset email. Future resetPassword(String email) { - return realmCore.emailPasswordResetPassword(app, email); + return app.handle.requestResetPassword(email); } /// Calls the reset password function, configured on the server. Future callResetPasswordFunction(String email, String password, {List? functionArgs}) { - return realmCore.emailPasswordCallResetPasswordFunction(app, email, password, functionArgs != null ? jsonEncode(functionArgs) : null); + return app.handle.callResetPasswordFunction(email, password, functionArgs.convert(jsonEncode)); } /// Retries the custom confirmation function on a user for a given email. Future retryCustomConfirmationFunction(String email) { - return realmCore.emailPasswordRetryCustomConfirmationFunction(app, email); + return app.handle.retryCustomConfirmationFunction(email); } } diff --git a/packages/realm_dart/lib/src/list.dart b/packages/realm_dart/lib/src/list.dart index de5257ae4..be64591cb 100644 --- a/packages/realm_dart/lib/src/list.dart +++ b/packages/realm_dart/lib/src/list.dart @@ -9,7 +9,11 @@ import 'dart:ffi'; import 'package:collection/collection.dart' as collection; import 'collections.dart'; -import 'native/realm_core.dart'; +import 'native/collection_changes_handle.dart'; +import 'native/handle_base.dart'; +import 'native/list_handle.dart'; +import 'native/notification_token_handle.dart'; +import 'native/object_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; import 'results.dart'; @@ -31,7 +35,7 @@ abstract class RealmList with RealmEntity implements List, /// Converts this [List] to a [RealmResults]. RealmResults asResults(); - factory RealmList._(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmList._(handle, realm, metadata); + factory RealmList._(ListHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmList._(handle, realm, metadata); /// Creates an unmanaged RealmList from [items] factory RealmList(Iterable items) => UnmanagedRealmList(items); @@ -44,7 +48,7 @@ abstract class RealmList with RealmEntity implements List, } class ManagedRealmList with RealmEntity, ListMixin implements RealmList { - final RealmListHandle _handle; + final ListHandle _handle; @override late final RealmObjectMetadata? _metadata; @@ -54,7 +58,7 @@ class ManagedRealmList with RealmEntity, ListMixin impleme } @override - int get length => realmCore.getListSize(handle); + int get length => handle.size; /// Setting the `length` is a required method on [List], but makes less sense /// for [RealmList]s. You can only decrease the length, increasing it doesn't @@ -99,12 +103,12 @@ class ManagedRealmList with RealmEntity, ListMixin impleme } try { - var value = realmCore.listGetElementAt(this, index); - if (value is RealmObjectHandle) { + var value = handle.elementAt(realm, index); + if (value is ObjectHandle) { late RealmObjectMetadata targetMetadata; late Type type; if (T == RealmValue) { - (type, targetMetadata) = realm.metadata.getByClassKey(realmCore.getClassKey(value)); + (type, targetMetadata) = realm.metadata.getByClassKey(value.classKey); } else { targetMetadata = _metadata!; type = T; @@ -140,19 +144,19 @@ class ManagedRealmList with RealmEntity, ListMixin impleme @override T removeAt(int index) { final result = this[index]; - realmCore.listRemoveElementAt(handle, index); + handle.removeAt(index); return result; } /// Move the element at index [from] to index [to]. void move(int from, int to) { - realmCore.listMoveElement(handle, from, to); + handle.move(from, to); } /// Removes all objects from this list; the length of the list becomes zero. /// The objects are not deleted from the realm, but are no longer referenced from this list. @override - void clear() => realmCore.listClear(handle); + void clear() => handle.clear(); @override int indexOf(covariant T element, [int start = 0]) { @@ -171,12 +175,12 @@ class ManagedRealmList with RealmEntity, ListMixin impleme } if (start < 0) start = 0; - final index = realmCore.listFind(this, element); + final index = handle.indexOf(element); return index < start ? -1 : index; // to align with dart list semantics } @override - bool get isValid => realmCore.listIsValid(this); + bool get isValid => handle.isValid; @override RealmList freeze() { @@ -189,7 +193,7 @@ class ManagedRealmList with RealmEntity, ListMixin impleme } @override - RealmResults asResults() => RealmResultsInternal.create(realmCore.resultsFromList(this), realm, metadata); + RealmResults asResults() => RealmResultsInternal.create(handle.asResults(), realm, metadata); @override Stream> get changes { @@ -244,7 +248,7 @@ extension RealmListOfObject on RealmList { /// /// For more details about the syntax of the Realm Query Language, refer to the documentation: https://www.mongodb.com/docs/realm/realm-query-language/. RealmResults query(String query, [List arguments = const []]) { - final handle = realmCore.queryList(asManaged(), query, arguments); + final handle = asManaged().handle.query(query, arguments); return RealmResultsInternal.create(handle, realm, _metadata); } } @@ -262,7 +266,7 @@ extension RealmListInternal on RealmList { ManagedRealmList asManaged() => this is ManagedRealmList ? this as ManagedRealmList : throw RealmStateError('$this is not managed'); - RealmListHandle get handle { + ListHandle get handle { final result = asManaged()._handle; if (result.released) { throw RealmClosedError('Cannot access a list that belongs to a closed Realm'); @@ -277,14 +281,14 @@ extension RealmListInternal on RealmList { return UnmanagedRealmList._(items); } - static RealmList create(RealmListHandle handle, Realm realm, RealmObjectMetadata? metadata) => RealmList._(handle, realm, metadata); + static RealmList create(ListHandle handle, Realm realm, RealmObjectMetadata? metadata) => RealmList._(handle, realm, metadata); - static void setValue(RealmListHandle handle, Realm realm, int index, Object? value, {bool update = false, bool insert = false}) { + static void setValue(ListHandle handle, Realm realm, int index, Object? value, {bool update = false, bool insert = false}) { if (index < 0) { throw RealmException("Index can not be negative: $index"); } - final length = realmCore.getListSize(handle); + final length = handle.size; if (index > length) { throw RealmException('Index can not exceed the size of the list: $index, size: $length'); } @@ -295,20 +299,19 @@ extension RealmListInternal on RealmList { throw RealmError("Can't add to list an embedded object that is already managed"); } - final objHandle = - insert || index >= length ? realmCore.listInsertEmbeddedObjectAt(realm, handle, index) : realmCore.listSetEmbeddedObjectAt(realm, handle, index); + final objHandle = insert || index >= length ? handle.insertEmbeddedAt(index) : handle.setEmbeddedAt(index); realm.manageEmbedded(objHandle, value); return; } if (value is RealmValue && value.type.isCollection) { - realmCore.listAddCollectionAt(handle, realm, index, value, insert || index >= length); + handle.addOrUpdateCollectionAt(realm, index, value, insert || index >= length); return; } realm.addUnmanagedRealmObjectFromValue(value, update); - realmCore.listAddElementAt(handle, index, value, insert || index >= length); + handle.addOrUpdateAt(index, value, insert || index >= length); } on Exception catch (e) { throw RealmException("Error setting value at index $index. Error: $e"); } @@ -334,8 +337,8 @@ class ListNotificationsController extends NotificationsContro ListNotificationsController(this.list); @override - RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeListNotifications(list, this); + NotificationTokenHandle subscribe() { + return list.handle.subscribeForNotifications(this); } Stream> createStream() { @@ -345,7 +348,7 @@ class ListNotificationsController extends NotificationsContro @override void onChanges(HandleBase changesHandle) { - if (changesHandle is! RealmCollectionChangesHandle) { + if (changesHandle is! CollectionChangesHandle) { throw RealmError("Invalid changes handle. RealmCollectionChangesHandle expected"); } diff --git a/packages/realm_dart/lib/src/map.dart b/packages/realm_dart/lib/src/map.dart index c7b9f7208..9e02bfb49 100644 --- a/packages/realm_dart/lib/src/map.dart +++ b/packages/realm_dart/lib/src/map.dart @@ -9,7 +9,11 @@ import 'package:collection/collection.dart' as collection; import 'dart:ffi'; import 'collections.dart'; -import 'native/realm_core.dart'; +import 'native/handle_base.dart'; +import 'native/map_changes_handle.dart'; +import 'native/map_handle.dart'; +import 'native/notification_token_handle.dart'; +import 'native/object_handle.dart'; import 'realm_object.dart'; import 'realm_class.dart'; import 'results.dart'; @@ -59,7 +63,7 @@ class UnmanagedRealmMap extends collection.DelegatingMap with RealmEntity, MapMixin implements RealmMap { - final RealmMapHandle _handle; + final MapHandle _handle; late final RealmObjectMetadata? _metadata; @@ -68,7 +72,7 @@ class ManagedRealmMap with RealmEntity, MapMixin i } @override - int get length => realmCore.mapGetSize(handle); + int get length => handle.size; @override T? remove(Object? key) { @@ -77,7 +81,7 @@ class ManagedRealmMap with RealmEntity, MapMixin i } final value = this[key]; - if (realmCore.mapRemoveKey(handle, key)) { + if (handle.remove(key)) { return value; } @@ -91,12 +95,12 @@ class ManagedRealmMap with RealmEntity, MapMixin i } try { - var value = realmCore.mapGetElement(this, key); - if (value is RealmObjectHandle) { + var value = handle.find(realm, key); + if (value is ObjectHandle) { late RealmObjectMetadata targetMetadata; late Type type; if (T == RealmValue) { - (type, targetMetadata) = realm.metadata.getByClassKey(realmCore.getClassKey(value)); + (type, targetMetadata) = realm.metadata.getByClassKey(value.classKey); } else { targetMetadata = _metadata!; type = T; @@ -126,10 +130,10 @@ class ManagedRealmMap with RealmEntity, MapMixin i /// Removes all objects from this map; the length of the map becomes zero. /// The objects are not deleted from the realm, but are no longer referenced from this map. @override - void clear() => realmCore.mapClear(handle); + void clear() => handle.clear(); @override - bool get isValid => realmCore.mapIsValid(this); + bool get isValid => handle.isValid; @override RealmMap freeze() { @@ -151,13 +155,13 @@ class ManagedRealmMap with RealmEntity, MapMixin i } @override - Iterable get keys => RealmResultsInternal.create(realmCore.mapGetKeys(this), realm, null); + Iterable get keys => RealmResultsInternal.create(handle.keys, realm, null); @override - Iterable get values => RealmResultsInternal.create(realmCore.mapGetValues(this), realm, metadata); + Iterable get values => RealmResultsInternal.create(handle.values, realm, metadata); @override - bool containsKey(Object? key) => key is String && realmCore.mapContainsKey(this, key); + bool containsKey(Object? key) => key is String && handle.containsKey(key); @override bool containsValue(Object? value) { @@ -179,7 +183,7 @@ class ManagedRealmMap with RealmEntity, MapMixin i } } - return realmCore.mapContainsValue(this, value); + return handle.containsValue(value); } } @@ -187,13 +191,10 @@ class ManagedRealmMap with RealmEntity, MapMixin i class RealmMapChanges { /// The collection being monitored for changes. final RealmMap map; + final MapChangesHandle handle; + late final MapChanges _changes = handle.changes; - final RealmMapChangesHandle _handle; - MapChanges? _values; - - RealmMapChanges._(this._handle, this.map); - - MapChanges get _changes => _values ??= realmCore.getMapChanges(_handle); + RealmMapChanges._(this.handle, this.map); /// The keys of the map which have been removed. List get deleted => _changes.deletions; @@ -220,7 +221,7 @@ extension RealmMapOfObject on RealmMap { /// /// For more details about the syntax of the Realm Query Language, refer to the documentation: https://www.mongodb.com/docs/realm/realm-query-language/. RealmResults query(String query, [List arguments = const []]) { - final handle = realmCore.queryMap(asManaged(), query, arguments); + final handle = asManaged().handle.query(query, arguments); return RealmResultsInternal.create(handle, realm, metadata); } } @@ -238,7 +239,7 @@ extension RealmMapInternal on RealmMap { ManagedRealmMap asManaged() => this is ManagedRealmMap ? this as ManagedRealmMap : throw RealmStateError('$this is not managed'); - RealmMapHandle get handle { + MapHandle get handle { final result = asManaged()._handle; if (result.released) { throw RealmClosedError('Cannot access a map that belongs to a closed Realm'); @@ -251,29 +252,28 @@ extension RealmMapInternal on RealmMap { static RealmMap createFromMap(Map map) => UnmanagedRealmMap._(map); - static RealmMap create(RealmMapHandle handle, Realm realm, RealmObjectMetadata? metadata) => - ManagedRealmMap._(handle, realm, metadata); + static RealmMap create(MapHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmMap._(handle, realm, metadata); - static void setValue(RealmMapHandle handle, Realm realm, String key, Object? value, {bool update = false}) { + static void setValue(MapHandle handle, Realm realm, String key, Object? value, {bool update = false}) { try { if (value is EmbeddedObject) { if (value.isManaged) { throw RealmError("Can't add to map an embedded object that is already managed"); } - final objHandle = realmCore.mapInsertEmbeddedObject(realm, handle, key); + final objHandle = handle.insertEmbedded(key); realm.manageEmbedded(objHandle, value); return; } if (value is RealmValue && value.type.isCollection) { - realmCore.mapInsertCollection(handle, realm, key, value); + handle.insertCollection(realm, key, value); return; } realm.addUnmanagedRealmObjectFromValue(value, update); - realmCore.mapInsertValue(handle, key, value); + handle.insert(key, value); } on Exception catch (e) { throw RealmException("Error setting value at key $key. Error: $e"); } @@ -288,8 +288,8 @@ class MapNotificationsController extends NotificationsControl MapNotificationsController(this.map); @override - RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeMapNotifications(map, this); + NotificationTokenHandle subscribe() { + return map.handle.subscribeForNotifications(this); } Stream> createStream() { @@ -299,7 +299,7 @@ class MapNotificationsController extends NotificationsControl @override void onChanges(HandleBase changesHandle) { - if (changesHandle is! RealmMapChangesHandle) { + if (changesHandle is! MapChangesHandle) { throw RealmError("Invalid changes handle. RealmMapChangesHandle expected"); } diff --git a/packages/realm_dart/lib/src/migration.dart b/packages/realm_dart/lib/src/migration.dart index 7c02226a1..86eaa0d50 100644 --- a/packages/realm_dart/lib/src/migration.dart +++ b/packages/realm_dart/lib/src/migration.dart @@ -1,9 +1,9 @@ // Copyright 2022 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 +import 'native/schema_handle.dart'; import 'realm_class.dart'; -import 'native/realm_core.dart'; -import './realm_object.dart'; +import 'realm_object.dart'; /// A [Migration] object is passed to you when you migrate your database from one version /// to another. It contains the properties for the Realm before and after the migration. @@ -33,7 +33,7 @@ class Migration { } final metadata = newRealm.metadata.getByType(T); - final handle = realmCore.findExisting(newRealm, metadata.classKey, oldObject.handle); + final handle = newRealm.handle.findExisting(metadata.classKey, oldObject.handle); if (handle == null) { return null; } @@ -45,7 +45,7 @@ class Migration { /// Renames a property during a migration. void renameProperty(String className, String oldPropertyName, String newPropertyName) { - realmCore.renameProperty(newRealm, className, oldPropertyName, newPropertyName, _schema); + newRealm.handle.renameProperty(className, oldPropertyName, newPropertyName, _schema); } /// Deletes a type during a migration. All the data associated with the type, as well as its schema, @@ -55,7 +55,7 @@ class Migration { /// /// Returns `true` if the table was present in the old Realm and was deleted. Returns `false` if it didn't exist. bool deleteType(String className) { - return realmCore.deleteType(newRealm, className); + return newRealm.handle.deleteType(className); } } diff --git a/packages/realm_dart/lib/src/native/app_handle.dart b/packages/realm_dart/lib/src/native/app_handle.dart new file mode 100644 index 000000000..c766c0453 --- /dev/null +++ b/packages/realm_dart/lib/src/native/app_handle.dart @@ -0,0 +1,427 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:isolate'; + +import '../init.dart'; +import '../realm_class.dart'; +import '../scheduler.dart'; +import 'convert.dart'; +import 'convert_native.dart'; +import 'credentials_handle.dart'; +import 'error_handling.dart'; +import 'ffi.dart'; +import 'handle_base.dart'; +import 'http_transport_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_core.dart'; +import 'realm_library.dart'; +import 'user_handle.dart'; + +class AppHandle extends HandleBase { + AppHandle(Pointer pointer) : super(pointer, 16); + + static bool _firstTime = true; + factory AppHandle.from(AppConfiguration configuration) { + // to avoid caching apps across hot restarts we clear the cache on the first + // time the ctor is called in the root isolate. + if (_firstTime && _isRootIsolate) { + _firstTime = false; + realmLib.realm_clear_cached_apps(); + } + final httpTransportHandle = HttpTransportHandle.from(configuration.httpClient); + final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); + return AppHandle(realmLib.realm_app_create_cached(appConfigHandle.pointer)); + } + + static AppHandle? get(String id, String? baseUrl) { + return using((arena) { + final outApp = arena>(); + realmLib + .realm_app_get_cached( + id.toCharPtr(arena), + baseUrl == null ? nullptr : baseUrl.toCharPtr(arena), + outApp, + ) + .raiseLastErrorIfFalse(); + return outApp.value.convert(AppHandle.new); + }); + } + + UserHandle? get currentUser { + return realmLib.realm_app_get_current_user(pointer).convert(UserHandle.new); + } + + List get users => using((arena) => _getUsers(arena)); + + List _getUsers(Arena arena, {int expectedSize = 2}) { + final actualCount = arena(); + final usersPtr = arena>(expectedSize); + realmLib.realm_app_get_all_users(pointer, usersPtr, expectedSize, actualCount).raiseLastErrorIfFalse(); + + if (expectedSize < actualCount.value) { + // The supplied array was too small - resize it + arena.free(usersPtr); + return _getUsers(arena, expectedSize: actualCount.value); + } + + final result = []; + for (var i = 0; i < actualCount.value; i++) { + result.add(UserHandle((usersPtr + i).value)); + } + + return result; + } + + Future removeUser(UserHandle user) { + final completer = Completer(); + realmLib + .realm_app_remove_user( + pointer, + user.pointer, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + return completer.future; + } + + void switchUser(UserHandle user) { + using((arena) { + realmLib + .realm_app_switch_user( + pointer, + user.pointer, + ) + .raiseLastErrorIfFalse(); + }); + } + + void reconnect() => realmLib.realm_app_sync_client_reconnect(pointer); + + String get baseUrl { + final customDataPtr = realmLib.realm_app_get_base_url(pointer); + return customDataPtr.cast().toRealmDartString(freeRealmMemory: true)!; + } + + Future updateBaseUrl(Uri? baseUrl) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_update_base_url( + pointer, + baseUrl?.toString().toCharPtr(arena) ?? nullptr, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future refreshCustomData(UserHandle user) { + final completer = Completer(); + realmLib + .realm_app_refresh_custom_data( + pointer, + user.pointer, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + return completer.future; + } + + String get id { + return realmLib.realm_app_get_app_id(pointer).cast().toRealmDartString()!; + } + + Future logIn(CredentialsHandle credentials) { + final completer = Completer(); + realmLib + .realm_app_log_in_with_credentials( + pointer, + credentials.pointer, + realmLib.addresses.realm_dart_user_completion_callback, + createAsyncUserCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + return completer.future; + } + + Future registerUser(String email, String password) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_register_email( + pointer, + email.toCharPtr(arena), + password.toRealmString(arena).ref, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future confirmUser(String token, String tokenId) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_confirm_user( + pointer, + token.toCharPtr(arena), + tokenId.toCharPtr(arena), + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future resendConfirmation(String email) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_resend_confirmation_email( + pointer, + email.toCharPtr(arena), + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future completeResetPassword(String password, String token, String tokenId) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_reset_password( + pointer, + password.toRealmString(arena).ref, + token.toCharPtr(arena), + tokenId.toCharPtr(arena), + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future requestResetPassword(String email) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_send_reset_password_email( + pointer, + email.toCharPtr(arena), + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future callResetPasswordFunction(String email, String password, String? argsAsJSON) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_call_reset_password_function( + pointer, + email.toCharPtr(arena), + password.toRealmString(arena).ref, + argsAsJSON != null ? argsAsJSON.toCharPtr(arena) : nullptr, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future retryCustomConfirmationFunction(String email) { + final completer = Completer(); + using((arena) { + realmLib + .realm_app_email_password_provider_client_retry_custom_confirmation( + pointer, + email.toCharPtr(arena), + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + }); + return completer.future; + } + + Future deleteUser(UserHandle user) { + final completer = Completer(); + realmLib + .realm_app_delete_user( + pointer, + user.pointer, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + return completer.future; + } + + bool resetRealm(String realmPath) { + return using((arena) { + final didRun = arena(); + realmLib + .realm_sync_immediately_run_file_actions( + pointer, + realmPath.toCharPtr(arena), + didRun, + ) + .raiseLastErrorIfFalse(); + return didRun.value; + }); + } + + Future callAppFunction(UserHandle user, String functionName, String? argsAsJSON) { + return using((arena) { + final completer = Completer(); + realmLib + .realm_app_call_function( + pointer, + user.pointer, + functionName.toCharPtr(arena), + argsAsJSON?.toCharPtr(arena) ?? nullptr, + nullptr, + realmLib.addresses.realm_dart_return_string_callback, + createAsyncFunctionCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + return completer.future; + }); + } +} + +Pointer createAsyncFunctionCallbackUserdata(Completer completer) { + final callback = Pointer.fromFunction< + Void Function( + Pointer, + Pointer, + Pointer, + )>(_callAppFunctionCallback); + + final userdata = realmLib.realm_dart_userdata_async_new( + completer, + callback.cast(), + scheduler.handle.pointer, + ); + + return userdata.cast(); +} + +void _callAppFunctionCallback(Pointer userdata, Pointer response, Pointer error) { + final Completer completer = userdata.toObject(); + + if (error != nullptr) { + completer.completeWithAppError(error); + return; + } + + final stringResponse = response.cast().toRealmDartString()!; + completer.complete(stringResponse); +} + +Pointer createAsyncCallbackUserdata(Completer completer) { + final callback = Pointer.fromFunction< + Void Function( + Pointer, + Pointer, + )>(_voidCompletionCallback); + + final userdata = realmLib.realm_dart_userdata_async_new( + completer, + callback.cast(), + scheduler.handle.pointer, + ); + + return userdata.cast(); +} + +void _voidCompletionCallback(Pointer userdata, Pointer error) { + final Completer completer = userdata.toObject(); + + if (error != nullptr) { + completer.completeWithAppError(error); + return; + } + + completer.complete(); +} + +class _AppConfigHandle extends HandleBase { + _AppConfigHandle(Pointer pointer) : super(pointer, 8); +} + +_AppConfigHandle _createAppConfig(AppConfiguration configuration, HttpTransportHandle httpTransport) { + return using((arena) { + final appId = configuration.appId.toCharPtr(arena); + final handle = _AppConfigHandle(realmLib.realm_app_config_new(appId, httpTransport.pointer)); + + realmLib.realm_app_config_set_platform_version(handle.pointer, Platform.operatingSystemVersion.toCharPtr(arena)); + + realmLib.realm_app_config_set_sdk(handle.pointer, 'Dart'.toCharPtr(arena)); + realmLib.realm_app_config_set_sdk_version(handle.pointer, libraryVersion.toCharPtr(arena)); + + final deviceName = realmCore.getDeviceName(); + realmLib.realm_app_config_set_device_name(handle.pointer, deviceName.toCharPtr(arena)); + + final deviceVersion = realmCore.getDeviceVersion(); + realmLib.realm_app_config_set_device_version(handle.pointer, deviceVersion.toCharPtr(arena)); + + realmLib.realm_app_config_set_framework_name(handle.pointer, (isFlutterPlatform ? 'Flutter' : 'Dart VM').toCharPtr(arena)); + realmLib.realm_app_config_set_framework_version(handle.pointer, Platform.version.toCharPtr(arena)); + + realmLib.realm_app_config_set_base_url(handle.pointer, configuration.baseUrl.toString().toCharPtr(arena)); + + realmLib.realm_app_config_set_default_request_timeout(handle.pointer, configuration.defaultRequestTimeout.inMilliseconds); + + realmLib.realm_app_config_set_bundle_id(handle.pointer, realmCore.getBundleId().toCharPtr(arena)); + + realmLib.realm_app_config_set_base_file_path(handle.pointer, configuration.baseFilePath.path.toCharPtr(arena)); + realmLib.realm_app_config_set_metadata_mode(handle.pointer, configuration.metadataPersistenceMode.index); + + if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { + realmLib.realm_app_config_set_metadata_encryption_key(handle.pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); + } + + return handle; + }); +} + +// TODO: +// We need a pure Dart equivalent of: +// ```dart +// ServiceBinding.rootIsolateToken != null +// ``` +// to get rid of this hack. +final bool _isRootIsolate = Isolate.current.debugName == 'main'; diff --git a/packages/realm_dart/lib/src/native/async_open_task_handle.dart b/packages/realm_dart/lib/src/native/async_open_task_handle.dart new file mode 100644 index 000000000..ddd184276 --- /dev/null +++ b/packages/realm_dart/lib/src/native/async_open_task_handle.dart @@ -0,0 +1,80 @@ +import 'dart:ffi'; + +import 'package:cancellation_token/cancellation_token.dart'; +import 'ffi.dart'; +import 'package:realm_dart/src/native/error_handling.dart'; +import 'package:realm_dart/src/native/realm_bindings.dart'; + +import '../realm_dart.dart'; +import '../scheduler.dart'; +import 'config_handle.dart'; +import 'handle_base.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'session_handle.dart'; + +class AsyncOpenTaskHandle extends HandleBase { + AsyncOpenTaskHandle(Pointer pointer) : super(pointer, 32); + + factory AsyncOpenTaskHandle.from(FlexibleSyncConfiguration config) { + final configHandle = ConfigHandle.from(config); + final asyncOpenTaskPtr = realmLib.realm_open_synchronized(configHandle.pointer).raiseLastErrorIfNull(); + return AsyncOpenTaskHandle(asyncOpenTaskPtr); + } + + Future openAsync(CancellationToken? cancellationToken) { + final completer = CancellableCompleter(cancellationToken); + if (!completer.isCancelled) { + final callback = + Pointer.fromFunction realm, Pointer error)>(_openRealmAsyncCallback); + final userData = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + realmLib.realm_async_open_task_start( + pointer, + realmLib.addresses.realm_dart_async_open_task_callback, + userData.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ); + } + return completer.future; + } + + void cancel() { + realmLib.realm_async_open_task_cancel(pointer); + } + + AsyncOpenTaskProgressNotificationTokenHandle registerProgressNotifier( + RealmAsyncOpenProgressNotificationsController controller, + ) { + final callback = Pointer.fromFunction(syncProgressCallback); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + return AsyncOpenTaskProgressNotificationTokenHandle( + realmLib.realm_async_open_task_register_download_progress_notifier( + pointer, + realmLib.addresses.realm_dart_sync_progress_callback, + userdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ), + ); + } +} + +class AsyncOpenTaskProgressNotificationTokenHandle extends HandleBase { + AsyncOpenTaskProgressNotificationTokenHandle(Pointer pointer) : super(pointer, 40); +} + +void _openRealmAsyncCallback(Object userData, Pointer realmSafePtr, Pointer error) { + return using((arena) { + final completer = userData as CancellableCompleter; + if (completer.isCancelled) { + return; + } + if (error != nullptr) { + final err = arena(); + final lastError = realmLib.realm_get_async_error(error, err) ? err.ref.toDart() : null; + completer.completeError(RealmException("Failed to open realm: ${lastError?.message ?? 'Error details missing.'}")); + return; + } + + completer.complete(RealmHandle(realmLib.realm_from_thread_safe_reference(realmSafePtr, scheduler.handle.pointer))); + }); +} diff --git a/packages/realm_dart/lib/src/native/collection_changes_handle.dart b/packages/realm_dart/lib/src/native/collection_changes_handle.dart new file mode 100644 index 000000000..ba5a63e29 --- /dev/null +++ b/packages/realm_dart/lib/src/native/collection_changes_handle.dart @@ -0,0 +1,78 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../collections.dart'; +import 'from_native.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +class CollectionChangesHandle extends HandleBase { + CollectionChangesHandle(Pointer pointer) : super(pointer, 256); + + CollectionChanges get changes { + return using((arena) { + final outNumDeletions = arena(); + final outNumInsertions = arena(); + final outNumModifications = arena(); + final outNumMoves = arena(); + final outCollectionCleared = arena(); + final outCollectionWasDeleted = arena(); + realmLib.realm_collection_changes_get_num_changes( + pointer, + outNumDeletions, + outNumInsertions, + outNumModifications, + outNumMoves, + outCollectionCleared, + outCollectionWasDeleted, + ); + + final deletionsCount = outNumDeletions != nullptr ? outNumDeletions.value : 0; + final insertionCount = outNumInsertions != nullptr ? outNumInsertions.value : 0; + final modificationCount = outNumModifications != nullptr ? outNumModifications.value : 0; + var moveCount = outNumMoves != nullptr ? outNumMoves.value : 0; + + final outDeletionIndexes = arena(deletionsCount); + final outInsertionIndexes = arena(insertionCount); + final outModificationIndexes = arena(modificationCount); + final outModificationIndexesAfter = arena(modificationCount); + final outMoves = arena(moveCount); + + realmLib.realm_collection_changes_get_changes( + pointer, + outDeletionIndexes, + deletionsCount, + outInsertionIndexes, + insertionCount, + outModificationIndexes, + modificationCount, + outModificationIndexesAfter, + modificationCount, + outMoves, + moveCount, + ); + + var elementZero = outMoves; + List moves = List.filled(moveCount, Move(elementZero.ref.from, elementZero.ref.to)); + for (var i = 1; i < moveCount; i++) { + final movePtr = outMoves + i; + moves[i] = Move(movePtr.ref.from, movePtr.ref.to); + } + + return CollectionChanges( + outDeletionIndexes.toIntList(deletionsCount), + outInsertionIndexes.toIntList(insertionCount), + outModificationIndexes.toIntList(modificationCount), + outModificationIndexesAfter.toIntList(modificationCount), + moves, + outCollectionCleared.value, + outCollectionWasDeleted.value, + ); + }); + } +} diff --git a/packages/realm_dart/lib/src/native/collection_handle_base.dart b/packages/realm_dart/lib/src/native/collection_handle_base.dart new file mode 100644 index 000000000..6ac5011c0 --- /dev/null +++ b/packages/realm_dart/lib/src/native/collection_handle_base.dart @@ -0,0 +1,51 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'package:realm_dart/src/native/rooted_handle.dart'; + +import '../realm_class.dart'; +import 'list_handle.dart'; +import 'map_handle.dart'; +import 'realm_bindings.dart'; + +abstract class CollectionHandleBase extends RootedHandleBase { + CollectionHandleBase(super.root, super.pointer, super.size); +} + +void createCollection(Realm realm, RealmValue value, Pointer Function() createList, Pointer Function() createMap) { + CollectionHandleBase? collectionHandle; + try { + switch (value.collectionType) { + case RealmCollectionType.list: + final listHandle = ListHandle(createList(), realm.handle); + collectionHandle = listHandle; + + final list = realm.createList(listHandle, null); + + // Necessary since Core will not clear the collection if the value was already a collection + list.clear(); + + for (final item in value.value as List) { + list.add(item); + } + case RealmCollectionType.map: + final mapHandle = MapHandle(createMap(), realm.handle); + collectionHandle = mapHandle; + + final map = realm.createMap(mapHandle, null); + + // Necessary since Core will not clear the collection if the value was already a collection + map.clear(); + + for (final kvp in (value.value as Map).entries) { + map[kvp.key] = kvp.value; + } + default: + throw RealmStateError('createCollection invoked with type that is not list or map.'); + } + } finally { + collectionHandle?.release(); + } +} diff --git a/packages/realm_dart/lib/src/native/config_handle.dart b/packages/realm_dart/lib/src/native/config_handle.dart new file mode 100644 index 000000000..bb4010a70 --- /dev/null +++ b/packages/realm_dart/lib/src/native/config_handle.dart @@ -0,0 +1,268 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; +import 'dart:ffi'; + +import 'ffi.dart'; +import 'package:realm_dart/src/native/error_handling.dart'; + +import '../configuration.dart'; +import '../migration.dart'; +import '../realm_class.dart'; +import '../scheduler.dart'; +import '../user.dart'; +import 'convert_native.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'schema_handle.dart'; + +class ConfigHandle extends HandleBase { + ConfigHandle(Pointer pointer) : super(pointer, 512); + + factory ConfigHandle.from(Configuration config) { + return using((arena) { + final configHandle = ConfigHandle(realmLib.realm_config_new()); + + if (config.schemaObjects.isNotEmpty) { + final schemaHandle = SchemaHandle.from(config.schemaObjects); + realmLib.realm_config_set_schema(configHandle.pointer, schemaHandle.pointer); + } + + realmLib.realm_config_set_path(configHandle.pointer, config.path.toCharPtr(arena)); + realmLib.realm_config_set_scheduler(configHandle.pointer, scheduler.handle.pointer); + + if (config.fifoFilesFallbackPath != null) { + realmLib.realm_config_set_fifo_path(configHandle.pointer, config.fifoFilesFallbackPath!.toCharPtr(arena)); + } + + // Setting schema version only makes sense for local realms, but core insists it is always set, + // hence we set it to 0 in those cases. + + final schemaVersion = switch (config) { + (LocalConfiguration lc) => lc.schemaVersion, + (FlexibleSyncConfiguration fsc) => fsc.schemaVersion, + _ => 0, + }; + realmLib.realm_config_set_schema_version(configHandle.pointer, schemaVersion); + if (config.maxNumberOfActiveVersions != null) { + realmLib.realm_config_set_max_number_of_active_versions(configHandle.pointer, config.maxNumberOfActiveVersions!); + } + if (config is LocalConfiguration) { + if (config.initialDataCallback != null) { + realmLib.realm_config_set_data_initialization_function( + configHandle.pointer, + Pointer.fromFunction(_initialDataCallback, false), + config.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + ); + } + if (config.isReadOnly) { + realmLib.realm_config_set_schema_mode(configHandle.pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); + } else if (config.shouldDeleteIfMigrationNeeded) { + realmLib.realm_config_set_schema_mode(configHandle.pointer, realm_schema_mode.RLM_SCHEMA_MODE_SOFT_RESET_FILE); + } + if (config.disableFormatUpgrade) { + realmLib.realm_config_set_disable_format_upgrade(configHandle.pointer, config.disableFormatUpgrade); + } + if (config.shouldCompactCallback != null) { + realmLib.realm_config_set_should_compact_on_launch_function( + configHandle.pointer, + Pointer.fromFunction(_shouldCompactCallback, false), + config.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + ); + } + if (config.migrationCallback != null) { + realmLib.realm_config_set_migration_function( + configHandle.pointer, + Pointer.fromFunction(_migrationCallback, false), + config.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + ); + } + } else if (config is InMemoryConfiguration) { + realmLib.realm_config_set_in_memory(configHandle.pointer, true); + } else if (config is FlexibleSyncConfiguration) { + realmLib.realm_config_set_schema_mode(configHandle.pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED); + final syncConfigPtr = realmLib.realm_flx_sync_config_new(config.user.handle.pointer).raiseLastErrorIfNull(); + try { + realmLib.realm_sync_config_set_session_stop_policy(syncConfigPtr, config.sessionStopPolicy.index); + realmLib.realm_sync_config_set_resync_mode(syncConfigPtr, config.clientResetHandler.clientResyncMode.index); + final errorHandlerCallback = + Pointer.fromFunction, realm_sync_error_t)>(_syncErrorHandlerCallback); + final errorHandlerUserdata = realmLib.realm_dart_userdata_async_new(config, errorHandlerCallback.cast(), scheduler.handle.pointer); + realmLib.realm_sync_config_set_error_handler(syncConfigPtr, realmLib.addresses.realm_dart_sync_error_handler_callback, errorHandlerUserdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free); + + if (config.clientResetHandler.onBeforeReset != null) { + final syncBeforeResetCallback = Pointer.fromFunction, Pointer)>(_syncBeforeResetCallback); + final beforeResetUserdata = realmLib.realm_dart_userdata_async_new(config, syncBeforeResetCallback.cast(), scheduler.handle.pointer); + + realmLib.realm_sync_config_set_before_client_reset_handler(syncConfigPtr, realmLib.addresses.realm_dart_sync_before_reset_handler_callback, + beforeResetUserdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); + } + + if (config.clientResetHandler.onAfterRecovery != null || config.clientResetHandler.onAfterDiscard != null) { + final syncAfterResetCallback = + Pointer.fromFunction, Pointer, Bool, Pointer)>( + _syncAfterResetCallback); + final afterResetUserdata = realmLib.realm_dart_userdata_async_new(config, syncAfterResetCallback.cast(), scheduler.handle.pointer); + + realmLib.realm_sync_config_set_after_client_reset_handler(syncConfigPtr, realmLib.addresses.realm_dart_sync_after_reset_handler_callback, + afterResetUserdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); + } + + if (config.shouldCompactCallback != null) { + realmLib.realm_config_set_should_compact_on_launch_function( + configHandle.pointer, + Pointer.fromFunction(_shouldCompactCallback, false), + config.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + ); + } + + realmLib.realm_config_set_sync_config(configHandle.pointer, syncConfigPtr); + } finally { + realmLib.realm_release(syncConfigPtr.cast()); + } + } else if (config is DisconnectedSyncConfiguration) { + realmLib.realm_config_set_schema_mode(configHandle.pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_EXPLICIT); + realmLib.realm_config_set_force_sync_history(configHandle.pointer, true); + } + + final key = config.encryptionKey; + if (key != null) { + realmLib.realm_config_set_encryption_key(configHandle.pointer, key.toUint8Ptr(arena), key.length); + } + + // For sync and for dynamic Realms, we need to have a complete view of the schema in Core. + if (config.schemaObjects.isEmpty || config is FlexibleSyncConfiguration) { + realmLib.realm_config_set_schema_subset_mode(configHandle.pointer, realm_schema_subset_mode.RLM_SCHEMA_SUBSET_MODE_COMPLETE); + } + + return configHandle; + }); + } +} + +void _syncAfterResetCallback(Object userdata, Pointer beforeHandle, Pointer afterReference, bool didRecover, + Pointer unlockCallbackFunc) { + _guardSynchronousCallback(() async { + final syncConfig = userdata as FlexibleSyncConfiguration; + final afterResetCallback = didRecover ? syncConfig.clientResetHandler.onAfterRecovery : syncConfig.clientResetHandler.onAfterDiscard; + + if (afterResetCallback == null) { + return; + } + + final beforeRealm = RealmInternal.getUnowned(syncConfig, RealmHandle.unowned(beforeHandle)); + final afterRealm = RealmInternal.getUnowned( + syncConfig, + RealmHandle.unowned(realmLib.realm_from_thread_safe_reference( + afterReference, + scheduler.handle.pointer, + ))); + + try { + await afterResetCallback(beforeRealm, afterRealm); + return; + } finally { + beforeRealm.handle.release(); + afterRealm.handle.release(); + } + }, unlockCallbackFunc); +} + +bool _shouldCompactCallback(Pointer userdata, int totalSize, int usedSize) { + final config = userdata.toObject(); + + if (config is LocalConfiguration) { + return config.shouldCompactCallback!(totalSize, usedSize); + } else if (config is FlexibleSyncConfiguration) { + return config.shouldCompactCallback!(totalSize, usedSize); + } + + return false; +} + +bool _migrationCallback(Pointer userdata, Pointer oldRealmHandle, Pointer newRealmHandle, Pointer schema) { + final oldHandle = RealmHandle.unowned(oldRealmHandle); + final newHandle = RealmHandle.unowned(newRealmHandle); + try { + final LocalConfiguration config = userdata.toObject(); + + final oldSchemaVersion = realmLib.realm_get_schema_version(oldRealmHandle); + final oldConfig = Configuration.local([], path: config.path, isReadOnly: true, schemaVersion: oldSchemaVersion); + final oldRealm = RealmInternal.getUnowned(oldConfig, oldHandle, isInMigration: true); + + final newRealm = RealmInternal.getUnowned(config, newHandle, isInMigration: true); + + final migration = MigrationInternal.create(RealmInternal.getMigrationRealm(oldRealm), newRealm, SchemaHandle.unowned(schema)); + config.migrationCallback!(migration, oldSchemaVersion); + return true; + } catch (ex) { + realmLib.realm_register_user_code_callback_error(ex.toPersistentHandle()); + } finally { + oldHandle.release(); + newHandle.release(); + } + + return false; +} + +void _syncErrorHandlerCallback(Object userdata, Pointer session, realm_sync_error error) { + final syncConfig = userdata as FlexibleSyncConfiguration; + // TODO: Take the app from the session instead of from syncConfig after fixing issue https://github.com/realm/realm-dart/issues/633 + final syncError = SyncErrorInternal.createSyncError(error.toDart(), app: syncConfig.user.app); + + if (syncError is ClientResetError) { + syncConfig.clientResetHandler.onManualReset?.call(syncError); + return; + } + + syncConfig.syncErrorHandler(syncError); +} + +void _syncBeforeResetCallback(Object userdata, Pointer realmPtr, Pointer unlockCallbackFunc) { + _guardSynchronousCallback(() async { + final syncConfig = userdata as FlexibleSyncConfiguration; + var beforeResetCallback = syncConfig.clientResetHandler.onBeforeReset!; + + final realm = RealmInternal.getUnowned(syncConfig, RealmHandle.unowned(realmPtr)); + try { + await beforeResetCallback(realm); + } finally { + realm.handle.release(); + } + }, unlockCallbackFunc); +} + +bool _initialDataCallback(Pointer userdata, Pointer realmPtr) { + final realmHandle = RealmHandle.unowned(realmPtr); + try { + final LocalConfiguration config = userdata.toObject(); + final realm = RealmInternal.getUnowned(config, realmHandle); + config.initialDataCallback!(realm); + return true; + } catch (ex) { + realmLib.realm_register_user_code_callback_error(ex.toPersistentHandle()); + } finally { + realmHandle.release(); + } + + return false; +} + +void _guardSynchronousCallback(FutureOr Function() callback, Pointer unlockCallbackFunc) async { + Pointer userError = nullptr; + try { + await callback(); + } catch (error) { + userError = error.toPersistentHandle(); + } finally { + realmLib.realm_dart_invoke_unlock_callback(userError, unlockCallbackFunc); + } +} diff --git a/packages/realm_dart/lib/src/native/convert.dart b/packages/realm_dart/lib/src/native/convert.dart new file mode 100644 index 000000000..34c258b22 --- /dev/null +++ b/packages/realm_dart/lib/src/native/convert.dart @@ -0,0 +1,24 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'handle_base.dart'; + +extension PointerEx on Pointer { + Pointer? get nullPtrAsNull => this == nullptr ? null : this; + U? convert(U Function(Pointer) convertor) => nullPtrAsNull.convert(convertor); +} + +extension HandleBaseEx on T { + T? get nullPtrAsNull => pointer == nullptr ? null : this; + U? convert(U Function(T) convertor) => nullPtrAsNull.convert(convertor); +} + +extension NullableObjectEx on T? { + U? convert(U Function(T) convertor) { + final self = this; + if (self == null) return null; + return convertor(self); + } +} diff --git a/packages/realm_dart/lib/src/native/convert_native.dart b/packages/realm_dart/lib/src/native/convert_native.dart new file mode 100644 index 000000000..6f7cb2aaf --- /dev/null +++ b/packages/realm_dart/lib/src/native/convert_native.dart @@ -0,0 +1,2 @@ +export 'to_native.dart'; +export 'from_native.dart'; diff --git a/packages/realm_dart/lib/src/native/credentials_handle.dart b/packages/realm_dart/lib/src/native/credentials_handle.dart new file mode 100644 index 000000000..d742d9ac9 --- /dev/null +++ b/packages/realm_dart/lib/src/native/credentials_handle.dart @@ -0,0 +1,82 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../credentials.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; +import 'to_native.dart'; + +class CredentialsHandle extends HandleBase { + CredentialsHandle(Pointer pointer) : super(pointer, 16); + + factory CredentialsHandle.anonymous(bool reuseCredentials) { + return CredentialsHandle(realmLib.realm_app_credentials_new_anonymous(reuseCredentials)); + } + + factory CredentialsHandle.emailPassword(String email, String password) { + return using((arena) { + final emailPtr = email.toCharPtr(arena); + final passwordPtr = password.toRealmString(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_email_password(emailPtr, passwordPtr.ref)); + }); + } + + factory CredentialsHandle.jwt(String token) { + return using((arena) { + final tokenPtr = token.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_jwt(tokenPtr)); + }); + } + + factory CredentialsHandle.apple(String idToken) { + return using((arena) { + final idTokenPtr = idToken.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_apple(idTokenPtr)); + }); + } + + factory CredentialsHandle.facebook(String accessToken) { + return using((arena) { + final accessTokenPtr = accessToken.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_facebook(accessTokenPtr)); + }); + } + + factory CredentialsHandle.googleIdToken(String idToken) { + return using((arena) { + final idTokenPtr = idToken.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_google_id_token(idTokenPtr)); + }); + } + + factory CredentialsHandle.googleAuthCode(String authCode) { + return using((arena) { + final authCodePtr = authCode.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_google_auth_code(authCodePtr)); + }); + } + + factory CredentialsHandle.function(String payload) { + return using((arena) { + final payloadPtr = payload.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_function(payloadPtr)); + }); + } + + factory CredentialsHandle.apiKey(String key) { + return using((arena) { + final keyPtr = key.toCharPtr(arena); + return CredentialsHandle(realmLib.realm_app_credentials_new_api_key(keyPtr)); + }); + } + + AuthProviderType get providerType { + final provider = realmLib.realm_auth_credentials_get_provider(pointer); + return AuthProviderTypeInternal.getByValue(provider); + } +} diff --git a/packages/realm_dart/lib/src/native/decimal128.dart b/packages/realm_dart/lib/src/native/decimal128.dart index 9d0039bbf..ad4df4acd 100644 --- a/packages/realm_dart/lib/src/native/decimal128.dart +++ b/packages/realm_dart/lib/src/native/decimal128.dart @@ -1,7 +1,15 @@ // Copyright 2023 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -part of 'realm_core.dart'; +import 'dart:convert'; +import 'dart:ffi'; + +import 'ffi.dart'; +import 'package:realm_common/realm_common.dart' as common; + +import 'realm_bindings.dart'; +import 'realm_library.dart'; +import 'to_native.dart'; /// A 128-bit decimal floating point number. class Decimal128 implements Comparable, common.Decimal128 { @@ -15,7 +23,7 @@ class Decimal128 implements Comparable, common.Decimal128 { static final ten = Decimal128.fromInt(10); /// The value NaN. - static final nan = Decimal128._(_realmLib.realm_dart_decimal128_nan()); + static final nan = Decimal128._(realmLib.realm_dart_decimal128_nan()); /// The value +Inf. static final infinity = one / zero; // +Inf @@ -33,7 +41,7 @@ class Decimal128 implements Comparable, common.Decimal128 { static Decimal128? tryParse(String source) { if (!_validInput.hasMatch(source)) return null; return using((arena) { - final result = _realmLib.realm_dart_decimal128_from_string(source.toCharPtr(arena)); + final result = realmLib.realm_dart_decimal128_from_string(source.toCharPtr(arena)); return Decimal128._(result); }); } @@ -45,7 +53,7 @@ class Decimal128 implements Comparable, common.Decimal128 { /// Converts a `int` into a [Decimal128]. factory Decimal128.fromInt(int value) { - return Decimal128._(_realmLib.realm_dart_decimal128_from_int64(value)); + return Decimal128._(realmLib.realm_dart_decimal128_from_int64(value)); } /// Converts a `double` into a [Decimal128]. @@ -54,30 +62,30 @@ class Decimal128 implements Comparable, common.Decimal128 { } /// Returns `true` if `this` is NaN. - bool get isNaN => _realmLib.realm_dart_decimal128_is_nan(_value); + bool get isNaN => realmLib.realm_dart_decimal128_is_nan(_value); /// Adds `this` with `other` and returns a new [Decimal128]. Decimal128 operator +(Decimal128 other) { - return Decimal128._(_realmLib.realm_dart_decimal128_add(_value, other._value)); + return Decimal128._(realmLib.realm_dart_decimal128_add(_value, other._value)); } /// Subtracts `other` from `this` and returns a new [Decimal128]. Decimal128 operator -(Decimal128 other) { - return Decimal128._(_realmLib.realm_dart_decimal128_subtract(_value, other._value)); + return Decimal128._(realmLib.realm_dart_decimal128_subtract(_value, other._value)); } /// Multiplies `this` with `other` and returns a new [Decimal128]. Decimal128 operator *(Decimal128 other) { - return Decimal128._(_realmLib.realm_dart_decimal128_multiply(_value, other._value)); + return Decimal128._(realmLib.realm_dart_decimal128_multiply(_value, other._value)); } /// Divides `this` by `other` and returns a new [Decimal128]. Decimal128 operator /(Decimal128 other) { - return Decimal128._(_realmLib.realm_dart_decimal128_divide(_value, other._value)); + return Decimal128._(realmLib.realm_dart_decimal128_divide(_value, other._value)); } /// Negates `this` and returns a new [Decimal128]. - Decimal128 operator -() => Decimal128._(_realmLib.realm_dart_decimal128_negate(_value)); + Decimal128 operator -() => Decimal128._(realmLib.realm_dart_decimal128_negate(_value)); /// Returns the absolute value of `this`. Decimal128 abs() => this < zero ? -this : this; @@ -89,14 +97,14 @@ class Decimal128 implements Comparable, common.Decimal128 { // WARNING: Don't use identical to ensure nan != nan, // if (identical(this, other)) return true; if (other is Decimal128) { - return _realmLib.realm_dart_decimal128_equal(_value, other._value); + return realmLib.realm_dart_decimal128_equal(_value, other._value); } return false; } /// Returns `true` if `this` is less than `other`. bool operator <(Decimal128 other) { - return _realmLib.realm_dart_decimal128_less_than(_value, other._value); + return realmLib.realm_dart_decimal128_less_than(_value, other._value); } /// Returns `true` if `this` is less than or equal to `other`. @@ -104,27 +112,27 @@ class Decimal128 implements Comparable, common.Decimal128 { /// Returns `true` if `this` is greater than `other`. bool operator >(Decimal128 other) { - return _realmLib.realm_dart_decimal128_greater_than(_value, other._value); + return realmLib.realm_dart_decimal128_greater_than(_value, other._value); } /// Returns `true` if `this` is greater than or equal to `other`. bool operator >=(Decimal128 other) => compareTo(other) >= 0; /// Converts `this` to an `int`. Possibly loosing precision. - int toInt() => _realmLib.realm_dart_decimal128_to_int64(_value); + int toInt() => realmLib.realm_dart_decimal128_to_int64(_value); /// String representation of `this`. @override String toString() { return using((arena) { - final realmString = _realmLib.realm_dart_decimal128_to_string(_value); + final realmString = realmLib.realm_dart_decimal128_to_string(_value); return ascii.decode(realmString.data.cast().asTypedList(realmString.size)); }); } /// Compares `this` to `other`. @override - int compareTo(Decimal128 other) => _realmLib.realm_dart_decimal128_compare_to(_value, other._value); + int compareTo(Decimal128 other) => realmLib.realm_dart_decimal128_compare_to(_value, other._value); } extension Decimal128Internal on Decimal128 { diff --git a/packages/realm_dart/lib/src/native/error_handling.dart b/packages/realm_dart/lib/src/native/error_handling.dart new file mode 100644 index 000000000..68f47fea8 --- /dev/null +++ b/packages/realm_dart/lib/src/native/error_handling.dart @@ -0,0 +1,69 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../realm_object.dart'; +import 'from_native.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +extension PointerEx on Pointer { + Pointer raiseLastErrorIfNull() { + if (this == nullptr) { + _raiseLastError(); + } + return this; + } +} + +extension BoolEx on bool { + void raiseLastErrorIfFalse() { + if (!this) { + _raiseLastError(); + } + } +} + +class LastError { + final int code; + final String? message; + final Object? userError; + + LastError(this.code, [this.message, this.userError]); + + @override + String toString() => "${message ?? 'No message'}. Error code: $code."; +} + +LastError? _getLastError(Allocator allocator) { + final error = allocator(); + final success = realmLib.realm_get_last_error(error); + return success ? error.ref.toDart() : null; +} + +Never _raiseLastError([String? errorMessage]) { + using((arena) { + final lastError = _getLastError(arena); + if (lastError?.userError != null) { + throw UserCallbackException(lastError!.userError!); + } + + final message = '${errorMessage != null ? "$errorMessage. " : ""}${lastError ?? ""}'; + switch (lastError?.code) { + case realm_errno.RLM_ERR_SCHEMA_MISMATCH: + throw MigrationRequiredException(message); + default: + throw RealmException(message); + } + }); +} + +extension RealmErrorEx on realm_error { + LastError toDart() { + final message = this.message.cast().toRealmDartString(); + return LastError(error, message, user_code_error.toUserCodeError()); + } +} diff --git a/packages/realm_dart/lib/src/native/ffi.dart b/packages/realm_dart/lib/src/native/ffi.dart new file mode 100644 index 000000000..7a1ae302c --- /dev/null +++ b/packages/realm_dart/lib/src/native/ffi.dart @@ -0,0 +1,4 @@ +// Import this file instead of package:ffi/ffi.dart +// Hides StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows +// silently allocating memory. Use toUtf8Ptr instead +export 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; diff --git a/packages/realm_dart/lib/src/native/from_native.dart b/packages/realm_dart/lib/src/native/from_native.dart new file mode 100644 index 000000000..316331ce2 --- /dev/null +++ b/packages/realm_dart/lib/src/native/from_native.dart @@ -0,0 +1,358 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'ffi.dart'; + +import '../app.dart'; +import '../configuration.dart'; +import '../realm_class.dart'; +import '../user.dart'; +import 'decimal128.dart'; +import 'list_handle.dart'; +import 'map_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +// TODO: Duplicated in to_native.dart +const int _microsecondsPerSecond = 1000 * 1000; +const int _nanosecondsPerMicrosecond = 1000; + +extension RealmValueEx on realm_value_t { + Object? toPrimitiveValue() => toDartValue(realm: null, getList: null, getMap: null); + + Object? toDartValue({required Realm? realm, required Pointer Function()? getList, required Pointer Function()? getMap}) { + switch (type) { + case realm_value_type.RLM_TYPE_NULL: + return null; + case realm_value_type.RLM_TYPE_INT: + return values.integer; + case realm_value_type.RLM_TYPE_BOOL: + return values.boolean; + case realm_value_type.RLM_TYPE_STRING: + return values.string.data.cast().toRealmDartString(length: values.string.size)!; + case realm_value_type.RLM_TYPE_FLOAT: + return values.fnum; + case realm_value_type.RLM_TYPE_DOUBLE: + return values.dnum; + case realm_value_type.RLM_TYPE_LINK: + if (realm == null) { + throw RealmError("A realm instance is required to resolve Backlinks"); + } + final objectKey = values.link.target; + final classKey = values.link.target_table; + if (realm.metadata.getByClassKeyIfExists(classKey) == null) return null; // temporary workaround to avoid crash on assertion + return realm.handle.getObject(classKey, objectKey); + case realm_value_type.RLM_TYPE_BINARY: + return Uint8List.fromList(values.binary.data.asTypedList(values.binary.size)); + case realm_value_type.RLM_TYPE_TIMESTAMP: + final seconds = values.timestamp.seconds; + final nanoseconds = values.timestamp.nanoseconds; + return DateTime.fromMicrosecondsSinceEpoch(seconds * _microsecondsPerSecond + nanoseconds ~/ _nanosecondsPerMicrosecond, isUtc: true); + case realm_value_type.RLM_TYPE_DECIMAL128: + var decimal = values.decimal128; // NOTE: Does not copy the struct! + decimal = realmLib.realm_dart_decimal128_copy(decimal); // This is a workaround to that + return Decimal128Internal.fromNative(decimal); + case realm_value_type.RLM_TYPE_OBJECT_ID: + return ObjectId.fromBytes(values.object_id.bytes.toList(12)); + case realm_value_type.RLM_TYPE_UUID: + final listInt = values.uuid.bytes.toList(16); + return Uuid.fromBytes(Uint8List.fromList(listInt).buffer); + case realm_value_type.RLM_TYPE_LIST: + if (getList == null || realm == null) { + throw RealmException('toDartValue called with a list argument but without a list getter'); + } + + final listHandle = ListHandle(getList(), realm.handle); + return realm.createList(listHandle, null); + case realm_value_type.RLM_TYPE_DICTIONARY: + if (getMap == null || realm == null) { + throw RealmException('toDartValue called with a list argument but without a list getter'); + } + + final mapHandle = MapHandle(getMap(), realm.handle); + return realm.createMap(mapHandle, null); + default: + throw RealmException("realm_value_type $type not supported"); + } + } +} + +extension ArrayUint8Ex on Array { + List toList(int count) { + final result = []; + for (var i = 0; i < count; i++) { + result.add(this[i]); + } + return result; + } +} + +extension PointerSizeEx on Pointer { + List toIntList(int count) { + List result = List.filled(count, value); + for (var i = 1; i < count; i++) { + result[i] = (this + i).value; + } + return result; + } +} + +extension PointerVoidEx on Pointer { + T toObject() { + assert(this != nullptr, "Pointer is null"); + + Object object = realmLib.realm_dart_persistent_handle_to_object(this); + + assert(object is T, "$T expected"); + return object as T; + } + + Object? toUserCodeError() { + if (this != nullptr) { + final result = toObject(); + realmLib.realm_dart_delete_persistent_handle(this); + return result; + } + + return null; + } +} + +extension PointerUtf8Ex on Pointer { + String? toRealmDartString({bool treatEmptyAsNull = false, int? length, bool freeRealmMemory = false}) { + if (this == nullptr) { + return null; + } + + try { + final result = toDartString(length: length); + + if (treatEmptyAsNull && result == '') { + return null; + } + return result; + } finally { + if (freeRealmMemory) { + realmLib.realm_free(cast()); + } + } + } +} + +extension RealmSyncErrorEx on realm_sync_error { + SyncErrorDetails toDart() { + final message = status.message.cast().toRealmDartString()!; + final userInfoMap = user_info_map.toDart(user_info_length); + final originalFilePathKey = c_original_file_path_key.cast().toRealmDartString(); + final recoveryFilePathKey = c_recovery_file_path_key.cast().toRealmDartString(); + + return SyncErrorDetails( + message, + status.error, + user_code_error.toUserCodeError(), + isFatal: is_fatal, + isClientResetRequested: is_client_reset_requested, + originalFilePath: userInfoMap?[originalFilePathKey], + backupFilePath: userInfoMap?[recoveryFilePathKey], + compensatingWrites: compensating_writes.toList(compensating_writes_length), + ); + } +} + +extension PointerRealmSyncErrorUserInfoEx on Pointer { + Map? toDart(int length) { + if (this == nullptr) { + return null; + } + Map userInfoMap = {}; + for (int i = 0; i < length; i++) { + final userInfoItem = this[i]; + final key = userInfoItem.key.cast().toDartString(); + final value = userInfoItem.value.cast().toDartString(); + userInfoMap[key] = value; + } + return userInfoMap; + } +} + +extension PointerRealmSyncErrorCompensatingWriteInfoEx on Pointer { + List? toList(int length) { + if (this == nullptr || length == 0) { + return null; + } + List compensatingWrites = []; + for (int i = 0; i < length; i++) { + final compensatingWrite = this[i]; + final reason = compensatingWrite.reason.cast().toDartString(); + final objectName = compensatingWrite.object_name.cast().toDartString(); + final primaryKey = compensatingWrite.primary_key.toPrimitiveValue(); + compensatingWrites.add(CompensatingWriteInfo(objectName, reason, RealmValue.from(primaryKey))); + } + return compensatingWrites; + } +} + +extension PointerRealmErrorEx on Pointer { + SyncError toDart() { + final message = ref.message.cast().toDartString(); + final details = SyncErrorDetails(message, ref.error, ref.user_code_error.toUserCodeError()); + return SyncErrorInternal.createSyncError(details); + } +} + +extension ObjectEx on Object { + Pointer toPersistentHandle() { + return realmLib.realm_dart_object_to_persistent_handle(this); + } +} + +extension ListUserStateEx on List { + UserState fromIndex(int index) { + if (!UserState.values.any((value) => value.index == index)) { + throw RealmError("Unknown user state $index"); + } + + return UserState.values[index]; + } +} + +extension RealmPropertyInfoEx on realm_property_info { + SchemaProperty toSchemaProperty() { + final linkTarget = link_target == nullptr ? null : link_target.cast().toDartString(); + return SchemaProperty(name.cast().toDartString(), RealmPropertyType.values[type], + optional: flags & realm_property_flags.RLM_PROPERTY_NULLABLE == realm_property_flags.RLM_PROPERTY_NULLABLE, + primaryKey: flags & realm_property_flags.RLM_PROPERTY_PRIMARY_KEY == realm_property_flags.RLM_PROPERTY_PRIMARY_KEY, + linkTarget: linkTarget == null || linkTarget.isEmpty ? null : linkTarget, + collectionType: RealmCollectionType.values[collection_type]); + } +} + +extension CompleterEx on Completer { + void completeFrom(FutureOr Function() action) { + try { + complete(action()); + } catch (error, stackTrace) { + completeError(error, stackTrace); + } + } + + void completeWithAppError(Pointer error) { + final message = error.ref.message.cast().toRealmDartString()!; + final linkToLogs = error.ref.link_to_server_logs.cast().toRealmDartString(); + completeError(AppInternal.createException(message, linkToLogs, error.ref.http_status_code)); + } +} + +enum CustomErrorCode { + noError(0), + unknownHttp(998), + unknown(999), + timeout(1000); + + final int code; + const CustomErrorCode(this.code); +} + +enum HttpMethod { + get, + post, + patch, + put, + delete, +} + +extension RealmTimestampEx on realm_timestamp_t { + DateTime toDart() { + return DateTime.fromMicrosecondsSinceEpoch(seconds * _microsecondsPerSecond + nanoseconds ~/ 1000, isUtc: true); + } +} + +extension RealmStringEx on realm_string_t { + String? toDart() => data.cast().toRealmDartString(); +} + +extension ObjectIdEx on ObjectId { + Pointer toNative(Allocator allocator) { + final result = allocator(); + for (var i = 0; i < 12; i++) { + result.ref.bytes[i] = bytes[i]; + } + return result; + } +} + +extension RealmObjectIdEx on realm_object_id { + ObjectId toDart() { + final buffer = Uint8List(12); + for (int i = 0; i < 12; ++i) { + buffer[i] = bytes[i]; + } + return ObjectId.fromBytes(buffer); + } +} + +extension RealmAppUserApikeyEx on realm_app_user_apikey { + ApiKey toDart() => UserInternal.createApiKey( + id.toDart(), + name.cast().toDartString(), + key.cast().toRealmDartString(treatEmptyAsNull: true), + !disabled, + ); +} + +extension PlatformEx on Platform { + static String fromEnvironment(String name, {String defaultValue = ""}) { + final result = Platform.environment[name]; + if (result == null) { + return defaultValue; + } + + return result; + } +} + +/// @nodoc +class SyncErrorDetails { + final String message; + final int code; + final String? path; + final bool isFatal; + final bool isClientResetRequested; + final String? originalFilePath; + final String? backupFilePath; + final List? compensatingWrites; + final Object? userError; + + SyncErrorDetails( + this.message, + this.code, + this.userError, { + this.path, + this.isFatal = false, + this.isClientResetRequested = false, + this.originalFilePath, + this.backupFilePath, + this.compensatingWrites, + }); +} + +extension PointerRealmValueEx on Pointer { + Object? toDartValue(Realm realm, Pointer Function()? getList, Pointer Function()? getMap) { + if (this == nullptr) { + throw RealmException("Can not convert nullptr realm_value to Dart value"); + } + return ref.toDartValue(realm: realm, getList: getList, getMap: getMap); + } + + List toStringList(int count) { + final result = List.filled(count, ''); + for (var i = 0; i < count; i++) { + final strValue = (this + i).ref.values.string; + result[i] = strValue.data.cast().toRealmDartString(length: strValue.size)!; + } + + return result; + } +} diff --git a/packages/realm_dart/lib/src/native/handle_base.dart b/packages/realm_dart/lib/src/native/handle_base.dart new file mode 100644 index 000000000..f4a3cbfe5 --- /dev/null +++ b/packages/realm_dart/lib/src/native/handle_base.dart @@ -0,0 +1,93 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'package:realm_dart/src/native/error_handling.dart'; + +import 'realm_library.dart'; + +// Flag to enable trace on finalization. +// +// Be aware that the trace is likely late, and it might in rare case be missing, +// as there are no absolute guarantees with Finalizer. +// +// It is often beneficial to also instrument the native realm_release to +// print the address released to get the exact time. +// +// This is const to allow the compiler to remove the trace code if not enabled. +const _enableFinalizerTrace = false; + +void _traceFinalization(Object o) { + print('Finalizing: $o'); +} + +final _debugFinalizer = Finalizer(_traceFinalization); + +void _setupFinalizationTrace(Object value, Object finalizationToken) { + _debugFinalizer.attach(value, finalizationToken, detach: value); +} + +void _tearDownFinalizationTrace(Object value, Object finalizationToken) { + _debugFinalizer.detach(value); + _traceFinalization(finalizationToken); +} + +abstract class HandleBase implements Finalizable { + late Pointer _finalizableHandle; + Pointer pointer; + bool get released => pointer == nullptr; + final bool isUnowned; + + @pragma('vm:never-inline') + void keepAlive() {} + + HandleBase(this.pointer, int size) : isUnowned = false { + pointer.raiseLastErrorIfNull(); + _finalizableHandle = realmLib.realm_attach_finalizer(this, pointer.cast(), size); + + if (_enableFinalizerTrace) { + _setupFinalizationTrace(this, pointer); + } + } + + HandleBase.unowned(this.pointer) : isUnowned = true { + pointer.raiseLastErrorIfNull(); + } + + @override + String toString() => "${pointer.toString()} value=${pointer.cast().value}${isUnowned ? ' (unowned)' : ''}"; + + /// @nodoc + /// A method that will be invoked just before the handle is released. Allows to cleanup + /// any custom data that inheritors are storing. + void releaseCore() {} + + void release() { + if (released) { + return; + } + + releaseCore(); + + if (!isUnowned) { + realmLib.realm_detach_finalizer(_finalizableHandle, this); + + realmLib.realm_release(pointer.cast()); + } + + pointer = nullptr; + + if (_enableFinalizerTrace) { + _tearDownFinalizationTrace(this, pointer); + } + } + + @override + // ignore: hash_and_equals + bool operator ==(Object other) => other is HandleBase + ? pointer == other.pointer + ? true + : realmLib.realm_equals(pointer.cast(), other.pointer.cast()) + : false; +} diff --git a/packages/realm_dart/lib/src/native/http_transport_handle.dart b/packages/realm_dart/lib/src/native/http_transport_handle.dart new file mode 100644 index 000000000..afb776bfd --- /dev/null +++ b/packages/realm_dart/lib/src/native/http_transport_handle.dart @@ -0,0 +1,153 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:convert'; +import 'dart:ffi'; +import 'dart:io'; + +import '../logging.dart'; +import '../realm_dart.dart'; +import '../scheduler.dart'; +import 'convert_native.dart'; +import 'ffi.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +class HttpTransportHandle extends HandleBase { + HttpTransportHandle(Pointer pointer) : super(pointer, 24); + + factory HttpTransportHandle.from(HttpClient httpClient) { + final requestCallback = Pointer.fromFunction)>(_requestCallback); + final requestCallbackUserdata = realmLib.realm_dart_userdata_async_new(httpClient, requestCallback.cast(), scheduler.handle.pointer); + return HttpTransportHandle(realmLib.realm_http_transport_new( + realmLib.addresses.realm_dart_http_request_callback, + requestCallbackUserdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + )); + } +} + +void _requestCallback(Object userData, realm_http_request request, Pointer requestContext) { + // + // The request struct only survives until end-of-call, even though + // we explicitly call realm_http_transport_complete_request to + // mark request as completed later. + // + // Therefore we need to copy everything out of request before returning. + // We cannot clone request on the native side with realm_clone, + // since realm_http_request does not inherit from WrapC. + + final client = userData as HttpClient; + + client.connectionTimeout = Duration(milliseconds: request.timeout_ms); + + final url = Uri.parse(request.url.cast().toRealmDartString()!); + + final body = request.body.cast().toRealmDartString(length: request.body_size); + + final headers = {}; + for (int i = 0; i < request.num_headers; ++i) { + final header = request.headers[i]; + final name = header.name.cast().toRealmDartString()!; + final value = header.value.cast().toRealmDartString()!; + headers[name] = value; + } + + _requestCallbackAsync(client, request.method, url, body, headers, requestContext); + // The request struct dies here! +} + +Future _requestCallbackAsync( + HttpClient client, + int requestMethod, + Uri url, + String? body, + Map headers, + Pointer requestContext, +) async { + await using((arena) async { + final responsePointer = arena(); + final responseRef = responsePointer.ref; + final method = HttpMethod.values[requestMethod]; + + try { + // Build request + late HttpClientRequest request; + + switch (method) { + case HttpMethod.delete: + request = await client.deleteUrl(url); + break; + case HttpMethod.put: + request = await client.putUrl(url); + break; + case HttpMethod.patch: + request = await client.patchUrl(url); + break; + case HttpMethod.post: + request = await client.postUrl(url); + break; + case HttpMethod.get: + request = await client.getUrl(url); + break; + } + + for (final header in headers.entries) { + request.headers.add(header.key, header.value); + } + + if (body != null) { + request.add(utf8.encode(body)); + } + + Realm.logger.log(LogLevel.debug, "HTTP Transport: Executing ${method.name} $url"); + + final stopwatch = Stopwatch()..start(); + + // Do the call.. + final response = await request.close(); + + stopwatch.stop(); + Realm.logger.log(LogLevel.debug, "HTTP Transport: Executed ${method.name} $url: ${response.statusCode} in ${stopwatch.elapsedMilliseconds} ms"); + + final responseBody = await response.fold>([], (acc, l) => acc..addAll(l)); // gather response + + // Report back to core + responseRef.status_code = response.statusCode; + responseRef.body = responseBody.toCharPtr(arena); + responseRef.body_size = responseBody.length; + + int headerCnt = 0; + response.headers.forEach((name, values) { + headerCnt += values.length; + }); + + responseRef.headers = arena(headerCnt); + responseRef.num_headers = headerCnt; + + int index = 0; + response.headers.forEach((name, values) { + for (final value in values) { + final headerRef = (responseRef.headers + index).ref; + headerRef.name = name.toCharPtr(arena); + headerRef.value = value.toCharPtr(arena); + index++; + } + }); + + responseRef.custom_status_code = CustomErrorCode.noError.code; + } on SocketException catch (socketEx) { + Realm.logger.log(LogLevel.warn, "HTTP Transport: SocketException executing ${method.name} $url: $socketEx"); + responseRef.custom_status_code = CustomErrorCode.timeout.code; + } on HttpException catch (httpEx) { + Realm.logger.log(LogLevel.warn, "HTTP Transport: HttpException executing ${method.name} $url: $httpEx"); + responseRef.custom_status_code = CustomErrorCode.unknownHttp.code; + } catch (ex) { + Realm.logger.log(LogLevel.error, "HTTP Transport: Exception executing ${method.name} $url: $ex"); + responseRef.custom_status_code = CustomErrorCode.unknown.code; + } finally { + realmLib.realm_http_transport_complete_request(requestContext, responsePointer); + } + }); +} diff --git a/packages/realm_dart/lib/src/native/list_handle.dart b/packages/realm_dart/lib/src/native/list_handle.dart new file mode 100644 index 000000000..5021b5abc --- /dev/null +++ b/packages/realm_dart/lib/src/native/list_handle.dart @@ -0,0 +1,146 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import '../realm_dart.dart'; +import 'collection_handle_base.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'ffi.dart'; +import 'notification_token_handle.dart'; +import 'object_handle.dart'; +import 'query_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; + +class ListHandle extends CollectionHandleBase { + ListHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 88); + + bool get isValid => realmLib.realm_list_is_valid(pointer); + + ResultsHandle asResults() { + return ResultsHandle(realmLib.realm_list_to_results(pointer), root); + } + + int get size { + return using((arena) { + final size = arena(); + realmLib.realm_list_size(pointer, size).raiseLastErrorIfFalse(); + return size.value; + }); + } + + void removeAt(int index) { + realmLib.realm_list_erase(pointer, index).raiseLastErrorIfFalse(); + } + + void move(int from, int to) { + realmLib.realm_list_move(pointer, from, to).raiseLastErrorIfFalse(); + } + + void deleteAll() { + realmLib.realm_list_remove_all(pointer).raiseLastErrorIfFalse(); + } + + int indexOf(Object? value) { + return using((arena) { + final outIndex = arena(); + final outFound = arena(); + + // TODO: how should this behave for collections + final realmValue = value.toNative(arena); + realmLib + .realm_list_find( + pointer, + realmValue, + outIndex, + outFound, + ) + .raiseLastErrorIfFalse(); + return outFound.value ? outIndex.value : -1; + }); + } + + void clear() { + realmLib.realm_list_clear(pointer).raiseLastErrorIfFalse(); + } + + // TODO: avoid taking the [realm] parameter + Object? elementAt(Realm realm, int index) { + return using((arena) { + final realmValue = arena(); + realmLib.realm_list_get(pointer, index, realmValue).raiseLastErrorIfFalse(); + return realmValue.toDartValue( + realm, + () => realmLib.realm_list_get_list(pointer, index), + () => realmLib.realm_list_get_dictionary(pointer, index), + ); + }); + } + + ListHandle? resolveIn(RealmHandle frozenRealm) { + return using((arena) { + final resultPtr = arena>(); + realmLib.realm_list_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); + return resultPtr == nullptr ? null : ListHandle(resultPtr.value, root); + }); + } + + // TODO: Consider splitting into two methods + void addOrUpdateAt(int index, Object? value, bool insert) { + using((arena) { + final realmValue = value.toNative(arena); + (insert ? realmLib.realm_list_insert : realmLib.realm_list_set)(pointer, index, realmValue.ref).raiseLastErrorIfFalse(); + }); + } + + // TODO: avoid taking the [realm] parameter + void addOrUpdateCollectionAt(Realm realm, int index, RealmValue value, bool insert) { + createCollection(realm, value, () => (insert ? realmLib.realm_list_insert_list : realmLib.realm_list_set_list)(pointer, index), + () => (insert ? realmLib.realm_list_insert_dictionary : realmLib.realm_list_set_dictionary)(pointer, index)); + } + + ObjectHandle setEmbeddedAt(int index) { + return ObjectHandle(realmLib.realm_list_set_embedded(pointer, index), root); + } + + ObjectHandle insertEmbeddedAt(int index) { + return ObjectHandle(realmLib.realm_list_insert_embedded(pointer, index), root); + } + + ResultsHandle query(String query, List args) { + return using((arena) { + final length = args.length; + final argsPointer = arena(length); + for (var i = 0; i < length; ++i) { + intoRealmQueryArg(args[i], argsPointer + i, arena); + } + final queryHandle = QueryHandle( + realmLib.realm_query_parse_for_list( + pointer, + query.toCharPtr(arena), + length, + argsPointer, + ), + root, + ); + return queryHandle.findAll(); + }); + } + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { + return NotificationTokenHandle( + realmLib.realm_list_add_notification_callback( + pointer, + controller.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + nullptr, + Pointer.fromFunction(collectionChangeCallback), + ), + root, + ); + } +} diff --git a/packages/realm_dart/lib/src/native/map_changes_handle.dart b/packages/realm_dart/lib/src/native/map_changes_handle.dart new file mode 100644 index 000000000..bccfbb909 --- /dev/null +++ b/packages/realm_dart/lib/src/native/map_changes_handle.dart @@ -0,0 +1,55 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'package:realm_dart/src/native/from_native.dart'; + +import '../collections.dart'; +import 'ffi.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +class MapChangesHandle extends HandleBase { + MapChangesHandle(Pointer pointer) : super(pointer, 256); + + MapChanges get changes { + return using((arena) { + final outNumDeletions = arena(); + final outNumInsertions = arena(); + final outNumModifications = arena(); + final outCollectionWasDeleted = arena(); + realmLib.realm_dictionary_get_changes( + pointer, + outNumDeletions, + outNumInsertions, + outNumModifications, + outCollectionWasDeleted, + ); + + final deletionsCount = outNumDeletions != nullptr ? outNumDeletions.value : 0; + final insertionCount = outNumInsertions != nullptr ? outNumInsertions.value : 0; + final modificationCount = outNumModifications != nullptr ? outNumModifications.value : 0; + + final outDeletionIndexes = arena(deletionsCount); + final outInsertionIndexes = arena(insertionCount); + final outModificationIndexes = arena(modificationCount); + final outCollectionWasCleared = arena(); + + realmLib.realm_dictionary_get_changed_keys( + pointer, + outDeletionIndexes, + outNumDeletions, + outInsertionIndexes, + outNumInsertions, + outModificationIndexes, + outNumModifications, + outCollectionWasCleared, + ); + + return MapChanges(outDeletionIndexes.toStringList(deletionsCount), outInsertionIndexes.toStringList(insertionCount), + outModificationIndexes.toStringList(modificationCount), outCollectionWasCleared.value, outCollectionWasDeleted.value); + }); + } +} diff --git a/packages/realm_dart/lib/src/native/map_handle.dart b/packages/realm_dart/lib/src/native/map_handle.dart new file mode 100644 index 000000000..27a96402a --- /dev/null +++ b/packages/realm_dart/lib/src/native/map_handle.dart @@ -0,0 +1,198 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import '../realm_dart.dart'; +import 'collection_handle_base.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'ffi.dart'; +import 'map_changes_handle.dart'; +import 'notification_token_handle.dart'; +import 'object_handle.dart'; +import 'query_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; + +class MapHandle extends CollectionHandleBase { + MapHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 96); // TODO: check size + + int get size { + return using((arena) { + final outSize = arena(); + realmLib.realm_dictionary_size(pointer, outSize).raiseLastErrorIfFalse(); + return outSize.value; + }); + } + + bool remove(String key) { + return using((arena) { + final keyNative = key.toNative(arena); + final outErased = arena(); + realmLib.realm_dictionary_erase(pointer, keyNative.ref, outErased).raiseLastErrorIfFalse(); + return outErased.value; + }); + } + + // TODO: avoid taking the [realm] parameter + Object? find(Realm realm, String key) { + return using((arena) { + final keyNative = key.toNative(arena); + final outValue = arena(); + final outFound = arena(); + realmLib.realm_dictionary_find(pointer, keyNative.ref, outValue, outFound).raiseLastErrorIfFalse(); + if (outFound.value) { + return outValue.toDartValue( + realm, + () => realmLib.realm_dictionary_get_list(pointer, keyNative.ref), + () => realmLib.realm_dictionary_get_dictionary(pointer, keyNative.ref), + ); + } + return null; + }); + } + + bool get isValid { + return realmLib.realm_dictionary_is_valid(pointer); + } + + void clear() { + realmLib.realm_dictionary_clear(pointer).raiseLastErrorIfFalse(); + } + + ResultsHandle get keys { + return using((arena) { + final outSize = arena(); + final outKeys = arena>(); + realmLib.realm_dictionary_get_keys(pointer, outSize, outKeys).raiseLastErrorIfFalse(); + return ResultsHandle(outKeys.value, root); + }); + } + + ResultsHandle get values { + return ResultsHandle(realmLib.realm_dictionary_to_results(pointer), root); + } + + bool containsKey(String key) { + return using((arena) { + final keyNative = key.toNative(arena); + final found = arena(); + realmLib.realm_dictionary_contains_key(pointer, keyNative.ref, found).raiseLastErrorIfFalse(); + return found.value; + }); + } + + int indexOf(Object? value) { + return using((arena) { + // TODO: how should this behave for collections + final valueNative = value.toNative(arena); + final index = arena(); + realmLib.realm_dictionary_contains_value(pointer, valueNative.ref, index).raiseLastErrorIfFalse(); + return index.value; + }); + } + + bool containsValue(Object? value) => indexOf(value) > -1; + + ObjectHandle insertEmbedded(String key) { + return using((arena) { + final keyNative = key.toNative(arena); + return ObjectHandle(realmLib.realm_dictionary_insert_embedded(pointer, keyNative.ref), root); + }); + } + + void insert(String key, Object? value) { + using((arena) { + final keyNative = key.toNative(arena); + final valueNative = value.toNative(arena); + realmLib + .realm_dictionary_insert( + pointer, + keyNative.ref, + valueNative.ref, + nullptr, + nullptr, + ) + .raiseLastErrorIfFalse(); + }); + } + + void insertCollection(Realm realm, String key, RealmValue value) { + using((arena) { + final keyNative = key.toNative(arena); + createCollection( + realm, + value, + () => realmLib.realm_dictionary_insert_list(pointer, keyNative.ref), + () => realmLib.realm_dictionary_insert_dictionary(pointer, keyNative.ref), + ); + }); + } + + ResultsHandle query(String query, List args) { + return using((arena) { + final length = args.length; + final argsPointer = arena(length); + for (var i = 0; i < length; ++i) { + intoRealmQueryArg(args[i], argsPointer + i, arena); + } + + final queryHandle = QueryHandle( + realmLib.realm_query_parse_for_results( + values.pointer, + query.toCharPtr(arena), + length, + argsPointer, + ), + root, + ); + return queryHandle.findAll(); + }); + } + + MapHandle? resolveIn(RealmHandle frozenRealm) { + return using((arena) { + final resultPtr = arena>(); + realmLib.realm_dictionary_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); + return resultPtr == nullptr ? null : MapHandle(resultPtr.value, root); + }); + } + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { + return NotificationTokenHandle( + realmLib.realm_dictionary_add_notification_callback( + pointer, + controller.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + nullptr, + Pointer.fromFunction(_mapChangeCallback), + ), + root, + ); + } +} + +void _mapChangeCallback(Pointer userdata, Pointer data) { + final NotificationsController controller = userdata.toObject(); + + if (data == nullptr) { + controller.onError(RealmError("Invalid notifications data received")); + return; + } + + try { + final clonedData = realmLib.realm_clone(data.cast()); + if (clonedData == nullptr) { + controller.onError(RealmError("Error while cloning notifications data")); + return; + } + + final changesHandle = MapChangesHandle(clonedData.cast()); + controller.onChanges(changesHandle); + } catch (e) { + controller.onError(RealmError("Error handling change notifications. Error: $e")); + } +} diff --git a/packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart b/packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart new file mode 100644 index 000000000..8e6a4aeab --- /dev/null +++ b/packages/realm_dart/lib/src/native/mutable_subscription_set_handle.dart @@ -0,0 +1,90 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../realm_dart.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; +import 'subscription_handle.dart'; +import 'subscription_set_handle.dart'; + +class MutableSubscriptionSetHandle extends SubscriptionSetHandle { + MutableSubscriptionSetHandle(Pointer pointer, RealmHandle root) : super(pointer.cast(), root); + + Pointer get _mutablePointer => super.pointer.cast(); + + SubscriptionSetHandle commit() => SubscriptionSetHandle(realmLib.realm_sync_subscription_set_commit(_mutablePointer), root); + + SubscriptionHandle insertOrAssignSubscription(ResultsHandle results, String? name, bool update) { + if (!update) { + if (name != null && findByName(name) != null) { + throw RealmException('Duplicate subscription with name: $name'); + } + } + return using((arena) { + final outIndex = arena(); + final outInserted = arena(); + realmLib + .realm_sync_subscription_set_insert_or_assign_results( + _mutablePointer, + results.pointer, + name?.toCharPtr(arena) ?? nullptr, + outIndex, + outInserted, + ) + .raiseLastErrorIfFalse(); + return this[outIndex.value]; + }); + } + + bool erase(SubscriptionHandle subscription) { + return using((arena) { + final outErased = arena(); + realmLib + .realm_sync_subscription_set_erase_by_id( + _mutablePointer, + subscription.id.toNative(arena), + outErased, + ) + .raiseLastErrorIfFalse(); + return outErased.value; + }); + } + + bool eraseByName(String name) { + return using((arena) { + final outErased = arena(); + realmLib + .realm_sync_subscription_set_erase_by_name( + _mutablePointer, + name.toCharPtr(arena), + outErased, + ) + .raiseLastErrorIfFalse(); + return outErased.value; + }); + } + + bool eraseByResults(ResultsHandle results) { + return using((arena) { + final outErased = arena(); + realmLib + .realm_sync_subscription_set_erase_by_results( + _mutablePointer, + results.pointer, + outErased, + ) + .raiseLastErrorIfFalse(); + return outErased.value; + }); + } + + void clear() => realmLib.realm_sync_subscription_set_clear(_mutablePointer).raiseLastErrorIfFalse(); +} diff --git a/packages/realm_dart/lib/src/native/notification_token_handle.dart b/packages/realm_dart/lib/src/native/notification_token_handle.dart new file mode 100644 index 000000000..eebee574e --- /dev/null +++ b/packages/realm_dart/lib/src/native/notification_token_handle.dart @@ -0,0 +1,35 @@ +import 'dart:ffi'; + +import '../realm_dart.dart'; +import 'collection_changes_handle.dart'; +import 'from_native.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'rooted_handle.dart'; + +class NotificationTokenHandle extends RootedHandleBase { + NotificationTokenHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 32); +} + +void collectionChangeCallback(Pointer userdata, Pointer data) { + final NotificationsController controller = userdata.toObject(); + + if (data == nullptr) { + controller.onError(RealmError("Invalid notifications data received")); + return; + } + + try { + final clonedData = realmLib.realm_clone(data.cast()); + if (clonedData == nullptr) { + controller.onError(RealmError("Error while cloning notifications data")); + return; + } + + final changesHandle = CollectionChangesHandle(clonedData.cast()); + controller.onChanges(changesHandle); + } catch (e) { + controller.onError(RealmError("Error handling change notifications. Error: $e")); + } +} diff --git a/packages/realm_dart/lib/src/native/object_handle.dart b/packages/realm_dart/lib/src/native/object_handle.dart new file mode 100644 index 000000000..f561e1815 --- /dev/null +++ b/packages/realm_dart/lib/src/native/object_handle.dart @@ -0,0 +1,206 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import '../realm_dart.dart'; +import 'collection_handle_base.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'ffi.dart'; +import 'handle_base.dart'; +import 'list_handle.dart'; +import 'map_handle.dart'; +import 'notification_token_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; +import 'rooted_handle.dart'; +import 'set_handle.dart'; + +class ObjectHandle extends RootedHandleBase { + ObjectHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 112); + + ObjectHandle createEmbedded(int propertyKey) { + return ObjectHandle(realmLib.realm_set_embedded(pointer, propertyKey), root); + } + + (ObjectHandle, int) get parent { + return using((arena) { + final parentPtr = arena>(); + final classKeyPtr = arena(); + realmLib.realm_object_get_parent(pointer, parentPtr, classKeyPtr).raiseLastErrorIfFalse(); + + final handle = ObjectHandle(parentPtr.value, root); + + return (handle, classKeyPtr.value); + }); + } + + int get classKey => realmLib.realm_object_get_table(pointer); + + bool get isValid => realmLib.realm_object_is_valid(pointer); + + Link get asLink { + final realmLink = realmLib.realm_object_as_link(pointer); + return Link(realmLink); + } + + // TODO: avoid taking the [realm] parameter + Object? getValue(Realm realm, int propertyKey) { + return using((arena) { + final realmValue = arena(); + realmLib.realm_get_value(pointer, propertyKey, realmValue).raiseLastErrorIfFalse(); + return realmValue.toDartValue( + realm, + () => realmLib.realm_get_list(pointer, propertyKey), + () => realmLib.realm_get_dictionary(pointer, propertyKey), + ); + }); + } + + // TODO: value should be RealmValue, and perhaps this method should be combined + // with setCollection? + void setValue(int propertyKey, Object? value, bool isDefault) { + using((arena) { + final realmValue = value.toNative(arena); + realmLib + .realm_set_value( + pointer, + propertyKey, + realmValue.ref, + isDefault, + ) + .raiseLastErrorIfFalse(); + }); + } + + ListHandle getList(int propertyKey) { + return ListHandle(realmLib.realm_get_list(pointer, propertyKey), root); + } + + SetHandle getSet(int propertyKey) { + return SetHandle(realmLib.realm_get_set(pointer, propertyKey), root); + } + + MapHandle getMap(int propertyKey) { + return MapHandle(realmLib.realm_get_dictionary(pointer, propertyKey), root); + } + + ResultsHandle getBacklinks(int sourceTableKey, int propertyKey) { + return ResultsHandle(realmLib.realm_get_backlinks(pointer, sourceTableKey, propertyKey), root); + } + + void setCollection(Realm realm, int propertyKey, RealmValue value) { + createCollection( + realm, + value, + () => realmLib.realm_set_list(pointer, propertyKey), + () => realmLib.realm_set_dictionary(pointer, propertyKey), + ); + } + + String objectToString() { + return realmLib.realm_object_to_string(pointer).cast().toRealmDartString(freeRealmMemory: true)!; + } + + void delete() { + realmLib.realm_object_delete(pointer).raiseLastErrorIfFalse(); + } + + ObjectHandle? resolveIn(RealmHandle frozenRealm) { + return using((arena) { + final resultPtr = arena>(); + realmLib.realm_object_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); + return resultPtr == nullptr ? null : ObjectHandle(resultPtr.value, frozenRealm); + }); + } + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller, [List? keyPaths]) { + return using((arena) { + final kpNative = buildAndVerifyKeyPath(keyPaths); + return NotificationTokenHandle( + realmLib.realm_object_add_notification_callback( + pointer, + controller.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + kpNative, + Pointer.fromFunction(_objectChangeCallback), + ), + root, + ); + }); + } + + Pointer buildAndVerifyKeyPath(List? keyPaths) { + return using((arena) { + if (keyPaths == null) { + return nullptr; + } + + final length = keyPaths.length; + final keypathsNative = arena>(length); + + for (int i = 0; i < length; i++) { + keypathsNative[i] = keyPaths[i].toCharPtr(arena); + } + // TODO(kn): + // call to classKey getter involves a native call, which is not ideal + return realmLib.realm_create_key_path_array(root.pointer, classKey, length, keypathsNative).raiseLastErrorIfNull(); + }); + } + + @override + // equals handled by HandleBase + // ignore: hash_and_equals + int get hashCode => asLink.hash; +} + +extension type Link(realm_link_t link) { + int get targetKey => link.target; + int get classKey => link.target_table; + + int get hash => Object.hash(targetKey, classKey); +} + +void _objectChangeCallback(Pointer userdata, Pointer data) { + final NotificationsController controller = userdata.toObject(); + + if (data == nullptr) { + controller.onError(RealmError("Invalid notifications data received")); + return; + } + + try { + final clonedData = realmLib.realm_clone(data.cast()); + if (clonedData == nullptr) { + controller.onError(RealmError("Error while cloning notifications data")); + return; + } + + final changesHandle = ObjectChangesHandle(clonedData.cast()); + controller.onChanges(changesHandle); + } catch (e) { + controller.onError(RealmError("Error handling change notifications. Error: $e")); + } +} + +class ObjectChangesHandle extends HandleBase { + ObjectChangesHandle(Pointer pointer) : super(pointer, 256); + + bool get isDeleted { + return realmLib.realm_object_changes_is_deleted(pointer); + } + + List get properties { + return using((arena) { + final count = realmLib.realm_object_changes_get_num_modified_properties(pointer); + + final outModified = arena(count); + realmLib.realm_object_changes_get_modified_properties(pointer, outModified, count); + + return outModified.asTypedList(count).toList(); + }); + } +} diff --git a/packages/realm_dart/lib/src/native/query_handle.dart b/packages/realm_dart/lib/src/native/query_handle.dart new file mode 100644 index 000000000..88a2e1856 --- /dev/null +++ b/packages/realm_dart/lib/src/native/query_handle.dart @@ -0,0 +1,22 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; +import 'rooted_handle.dart'; + +class QueryHandle extends RootedHandleBase { + QueryHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 256); + + ResultsHandle findAll() { + try { + return ResultsHandle(realmLib.realm_query_find_all(pointer), root); + } finally { + release(); + } + } +} diff --git a/packages/realm_dart/lib/src/native/realm_core.dart b/packages/realm_dart/lib/src/native/realm_core.dart index a8415d76a..a05b4d1ab 100644 --- a/packages/realm_dart/lib/src/native/realm_core.dart +++ b/packages/realm_dart/lib/src/native/realm_core.dart @@ -1,58 +1,23 @@ // Copyright 2021 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 -// ignore_for_file: non_constant_identifier_names - -import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; import 'dart:io'; -import 'dart:isolate'; -import 'dart:typed_data'; -import 'package:cancellation_token/cancellation_token.dart'; import 'package:crypto/crypto.dart'; -// Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead -import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:path/path.dart' as path; import 'package:pubspec_parse/pubspec_parse.dart'; -import 'package:realm_common/realm_common.dart' as common show Decimal128; -import 'package:realm_common/realm_common.dart' hide Decimal128; -import 'package:realm_dart/src/logging.dart'; -import '../app.dart'; -import '../collections.dart'; -import '../configuration.dart'; -import '../credentials.dart'; import '../init.dart'; -import '../list.dart'; -import '../map.dart'; -import '../migration.dart'; import '../realm_class.dart'; -import '../realm_object.dart'; -import '../results.dart'; import '../scheduler.dart'; -import '../session.dart'; -import '../set.dart'; -import '../subscription.dart'; -import '../user.dart'; -import 'realm_bindings.dart'; - -part 'decimal128.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'ffi.dart'; +import 'realm_library.dart'; -const bugInTheSdkMessage = "This is likely a bug in the Realm SDK - please file an issue at https://github.com/realm/realm-dart/issues"; - -// This variable allows access to realm native library even before RealmCore is created. For Decimal128 for example -final _realmLib = () { - final result = RealmLibrary(initRealm()); - final nativeLibraryVersion = result.realm_dart_library_version().cast().toDartString(); - if (libraryVersion != nativeLibraryVersion) { - final additionMessage = - isFlutterPlatform ? bugInTheSdkMessage : "Did you forget to run `dart run realm_dart install` after upgrading the realm_dart package?"; - throw RealmException('Realm SDK package version does not match the native library version ($libraryVersion != $nativeLibraryVersion). $additionMessage'); - } - return result; -}(); +final realmCore = RealmCore._(); final _pluginLib = () { if (!isFlutterPlatform) { @@ -75,730 +40,108 @@ final _pluginLib = () { return pluginLib; }(); -// stamped into the library by the build system (see prepare-release.yml) -const libraryVersion = '2.3.0'; - -_RealmCore realmCore = _RealmCore(); - -// All access to Realm Core functionality goes through this class -class _RealmCore { - // From realm.h. Currently not exported from the shared library - // ignore: unused_field, constant_identifier_names - static const int RLM_INVALID_CLASS_KEY = 0x7FFFFFFF; - // ignore: unused_field, constant_identifier_names - static const int RLM_INVALID_PROPERTY_KEY = -1; - // ignore: unused_field, constant_identifier_names - static const int RLM_INVALID_OBJECT_KEY = -1; - - final encryptionKeySize = 64; - // ignore: unused_field - static late final _RealmCore _instance; +class RealmCore { + RealmCore._(); - _RealmCore() { - // This disables creation of a second _RealmCore instance effectivelly making `realmCore` global variable readonly - _instance = this; - - // This prevents reentrance if `realmCore` global variable is accessed during _RealmCore construction - realmCore = this; - - _realmLib.realm_dart_init_debug_logger(); - } - - void loggerAttach() { - _realmLib.realm_dart_attach_logger(scheduler.nativePort); - } + // For debugging + int get threadId => realmLib.realm_dart_get_thread_id(); - void loggerDetach() { - _realmLib.realm_dart_detach_logger(scheduler.nativePort); + void clearCachedApps() { + realmLib.realm_clear_cached_apps(); } // for debugging only. Enable in realm_dart.cpp // void invokeGC() { - // _realmLib.realm_dart_gc(); + // realmLib.realm_dart_gc(); // } - LastError? getLastError(Allocator allocator) { - final error = allocator(); - final success = _realmLib.realm_get_last_error(error); - if (!success) { - return null; - } - - return error.ref.toLastError(); - } - - void throwLastError([String? errorMessage]) { - using((Arena arena) { - final lastError = getLastError(arena); - if (lastError?.userError != null) { - throw UserCallbackException(lastError!.userError!); - } - - final message = '${errorMessage != null ? "$errorMessage. " : ""}${lastError ?? ""}'; - switch (lastError?.code) { - case realm_errno.RLM_ERR_SCHEMA_MISMATCH: - throw MigrationRequiredException(message); - default: - throw RealmException(message); - } + void deleteRealmFiles(String path) { + using((arena) { + final realmDeleted = arena(); + realmLib.realm_delete_files(path.toCharPtr(arena), realmDeleted).raiseLastErrorIfFalse(); }); } - SchemaHandle _createSchema(Iterable schema) { - return using((Arena arena) { - final classCount = schema.length; - - final schemaClasses = arena(classCount); - final schemaProperties = arena>(classCount); - - for (var i = 0; i < classCount; i++) { - final schemaObject = schema.elementAt(i); - final classInfo = schemaClasses.elementAt(i).ref; - final propertiesCount = schemaObject.length; - final computedCount = schemaObject.where((p) => p.isComputed).length; - final persistedCount = propertiesCount - computedCount; - - classInfo.name = schemaObject.name.toCharPtr(arena); - classInfo.primary_key = "".toCharPtr(arena); - classInfo.num_properties = persistedCount; - classInfo.num_computed_properties = computedCount; - classInfo.key = RLM_INVALID_CLASS_KEY; - classInfo.flags = schemaObject.baseType.flags; - - final properties = arena(propertiesCount); - - for (var j = 0; j < propertiesCount; j++) { - final schemaProperty = schemaObject[j]; - final propInfo = properties.elementAt(j).ref; - propInfo.name = schemaProperty.mapTo.toCharPtr(arena); - propInfo.public_name = (schemaProperty.mapTo != schemaProperty.name ? schemaProperty.name : '').toCharPtr(arena); - propInfo.link_target = (schemaProperty.linkTarget ?? "").toCharPtr(arena); - propInfo.link_origin_property_name = (schemaProperty.linkOriginProperty ?? "").toCharPtr(arena); - propInfo.type = schemaProperty.propertyType.index; - propInfo.collection_type = schemaProperty.collectionType.index; - propInfo.flags = realm_property_flags.RLM_PROPERTY_NORMAL; - - if (schemaProperty.optional) { - propInfo.flags |= realm_property_flags.RLM_PROPERTY_NULLABLE; - } - - switch (schemaProperty.indexType) { - case RealmIndexType.regular: - propInfo.flags |= realm_property_flags.RLM_PROPERTY_INDEXED; - break; - case RealmIndexType.fullText: - propInfo.flags |= realm_property_flags.RLM_PROPERTY_FULLTEXT_INDEXED; - break; - default: - break; - } - - if (schemaProperty.primaryKey) { - classInfo.primary_key = schemaProperty.mapTo.toCharPtr(arena); - propInfo.flags |= realm_property_flags.RLM_PROPERTY_PRIMARY_KEY; - } - } - - schemaProperties[i] = properties; - schemaProperties.elementAt(i).value = properties; - } - - final schemaPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_schema_new(schemaClasses, classCount, schemaProperties)); - return SchemaHandle._(schemaPtr); + List getAllCategoryNames() { + return using((arena) { + final count = realmLib.realm_get_category_names(0, nullptr); + final outValues = arena>(count); + realmLib.realm_get_category_names(count, outValues); + return [for (int i = 0; i < count; i++) outValues[i].cast().toDartString()]; }); } - ConfigHandle _createConfig(Configuration config) { - return using((Arena arena) { - final configPtr = _realmLib.realm_config_new(); - final configHandle = ConfigHandle._(configPtr); - - if (config.schemaObjects.isNotEmpty) { - final schemaHandle = _createSchema(config.schemaObjects); - _realmLib.realm_config_set_schema(configHandle._pointer, schemaHandle._pointer); - } - - _realmLib.realm_config_set_path(configHandle._pointer, config.path.toCharPtr(arena)); - _realmLib.realm_config_set_scheduler(configHandle._pointer, scheduler.handle._pointer); - - if (config.fifoFilesFallbackPath != null) { - _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toCharPtr(arena)); - } - - // Setting schema version only makes sense for local realms, but core insists it is always set, - // hence we set it to 0 in those cases. - - final schemaVersion = switch (config) { - (LocalConfiguration lc) => lc.schemaVersion, - (FlexibleSyncConfiguration fsc) => fsc.schemaVersion, - _ => 0, - }; - _realmLib.realm_config_set_schema_version(configHandle._pointer, schemaVersion); - if (config.maxNumberOfActiveVersions != null) { - _realmLib.realm_config_set_max_number_of_active_versions(configHandle._pointer, config.maxNumberOfActiveVersions!); + String getAppDirectory() { + try { + if (!isFlutterPlatform || Platform.environment.containsKey('FLUTTER_TEST')) { + return Directory.current.absolute.path; // dart or flutter test } - if (config is LocalConfiguration) { - //_realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED); - if (config.initialDataCallback != null) { - _realmLib.realm_config_set_data_initialization_function( - configHandle._pointer, - Pointer.fromFunction(initial_data_callback, false), - config.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - ); - } - if (config.isReadOnly) { - _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); - } else if (config.shouldDeleteIfMigrationNeeded) { - _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_SOFT_RESET_FILE); - } - if (config.disableFormatUpgrade) { - _realmLib.realm_config_set_disable_format_upgrade(configHandle._pointer, config.disableFormatUpgrade); - } - if (config.shouldCompactCallback != null) { - _realmLib.realm_config_set_should_compact_on_launch_function( - configHandle._pointer, - Pointer.fromFunction(should_compact_callback, false), - config.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - ); - } - if (config.migrationCallback != null) { - _realmLib.realm_config_set_migration_function( - configHandle._pointer, - Pointer.fromFunction(migration_callback, false), - config.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - ); - } - } else if (config is InMemoryConfiguration) { - _realmLib.realm_config_set_in_memory(configHandle._pointer, true); - } else if (config is FlexibleSyncConfiguration) { - _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_DISCOVERED); - final syncConfigPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_flx_sync_config_new(config.user.handle._pointer)); - try { - _realmLib.realm_sync_config_set_session_stop_policy(syncConfigPtr, config.sessionStopPolicy.index); - _realmLib.realm_sync_config_set_resync_mode(syncConfigPtr, config.clientResetHandler.clientResyncMode.index); - final errorHandlerCallback = - Pointer.fromFunction, realm_sync_error_t)>(_syncErrorHandlerCallback); - final errorHandlerUserdata = _realmLib.realm_dart_userdata_async_new(config, errorHandlerCallback.cast(), scheduler.handle._pointer); - _realmLib.realm_sync_config_set_error_handler(syncConfigPtr, _realmLib.addresses.realm_dart_sync_error_handler_callback, errorHandlerUserdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free); - - if (config.clientResetHandler.onBeforeReset != null) { - final syncBeforeResetCallback = Pointer.fromFunction, Pointer)>(_syncBeforeResetCallback); - final beforeResetUserdata = _realmLib.realm_dart_userdata_async_new(config, syncBeforeResetCallback.cast(), scheduler.handle._pointer); - - _realmLib.realm_sync_config_set_before_client_reset_handler(syncConfigPtr, _realmLib.addresses.realm_dart_sync_before_reset_handler_callback, - beforeResetUserdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - } - if (config.clientResetHandler.onAfterRecovery != null || config.clientResetHandler.onAfterDiscard != null) { - final syncAfterResetCallback = - Pointer.fromFunction, Pointer, Bool, Pointer)>( - _syncAfterResetCallback); - final afterResetUserdata = _realmLib.realm_dart_userdata_async_new(config, syncAfterResetCallback.cast(), scheduler.handle._pointer); - - _realmLib.realm_sync_config_set_after_client_reset_handler(syncConfigPtr, _realmLib.addresses.realm_dart_sync_after_reset_handler_callback, - afterResetUserdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - } - - if (config.shouldCompactCallback != null) { - _realmLib.realm_config_set_should_compact_on_launch_function( - configHandle._pointer, - Pointer.fromFunction(should_compact_callback, false), - config.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - ); - } + // Flutter from here on.. - _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); - } finally { - _realmLib.realm_release(syncConfigPtr.cast()); - } - } else if (config is DisconnectedSyncConfiguration) { - _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_ADDITIVE_EXPLICIT); - _realmLib.realm_config_set_force_sync_history(configPtr, true); + if (Platform.isAndroid || Platform.isIOS) { + return _getFilesPath(); } - if (config.encryptionKey != null) { - _realmLib.realm_config_set_encryption_key(configPtr, config.encryptionKey!.toUint8Ptr(arena), encryptionKeySize); + if (Platform.isLinux) { + final appSupportDir = Platform.environment['XDG_DATA_HOME'] ?? Platform.environment['HOME'] ?? Directory.current.absolute.path; + return path.join(appSupportDir, ".local/share", _getAppDirectoryFromPlugin()); } - // For sync and for dynamic Realms, we need to have a complete view of the schema in Core. - if (config.schemaObjects.isEmpty || config is FlexibleSyncConfiguration) { - _realmLib.realm_config_set_schema_subset_mode(configHandle._pointer, realm_schema_subset_mode.RLM_SCHEMA_SUBSET_MODE_COMPLETE); + if (Platform.isMacOS) { + return _getAppDirectoryFromPlugin(); } - return configHandle; - }); - } - - String getPathForUser(User user) { - final syncConfigPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_flx_sync_config_new(user.handle._pointer)); - try { - final path = _realmLib.realm_app_sync_client_get_default_file_path_for_realm(syncConfigPtr, nullptr); - return path.cast().toRealmDartString(freeRealmMemory: true)!; - } finally { - _realmLib.realm_release(syncConfigPtr.cast()); - } - } - - ObjectId subscriptionId(Subscription subscription) { - final id = _realmLib.realm_sync_subscription_id(subscription.handle._pointer); - return id.toDart(); - } - - String? subscriptionName(Subscription subscription) { - final name = _realmLib.realm_sync_subscription_name(subscription.handle._pointer); - return name.toDart(); - } - - String subscriptionObjectClassName(Subscription subscription) { - final objectClassName = _realmLib.realm_sync_subscription_object_class_name(subscription.handle._pointer); - return objectClassName.toDart()!; - } - - String subscriptionQueryString(Subscription subscription) { - final queryString = _realmLib.realm_sync_subscription_query_string(subscription.handle._pointer); - return queryString.toDart()!; - } - - DateTime subscriptionCreatedAt(Subscription subscription) { - final createdAt = _realmLib.realm_sync_subscription_created_at(subscription.handle._pointer); - return createdAt.toDart(); - } - - DateTime subscriptionUpdatedAt(Subscription subscription) { - final updatedAt = _realmLib.realm_sync_subscription_updated_at(subscription.handle._pointer); - return updatedAt.toDart(); - } - - SubscriptionSetHandle getSubscriptions(Realm realm) { - return SubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_get_active_subscription_set(realm.handle._pointer)), realm.handle); - } - - void refreshSubscriptions(SubscriptionSet subscriptions) { - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_refresh(subscriptions.handle._pointer)); - } - - int getSubscriptionSetSize(SubscriptionSet subscriptions) { - return _realmLib.realm_sync_subscription_set_size(subscriptions.handle._pointer); - } - - Exception? getSubscriptionSetError(SubscriptionSet subscriptions) { - final error = _realmLib.realm_sync_subscription_set_error_str(subscriptions.handle._pointer); - final message = error.cast().toRealmDartString(treatEmptyAsNull: true); - return message == null ? null : RealmException(message); - } - - SubscriptionHandle subscriptionAt(SubscriptionSet subscriptions, int index) { - return SubscriptionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_subscription_at( - subscriptions.handle._pointer, - index, - ))); - } - - SubscriptionHandle? findSubscriptionByName(SubscriptionSet subscriptions, String name) { - return using((arena) { - final result = _realmLib.realm_sync_find_subscription_by_name( - subscriptions.handle._pointer, - name.toCharPtr(arena), - ); - return result == nullptr ? null : SubscriptionHandle._(result); - }); - } - - SubscriptionHandle? findSubscriptionByResults(SubscriptionSet subscriptions, RealmResults results) { - final result = _realmLib.realm_sync_find_subscription_by_results( - subscriptions.handle._pointer, - results.handle._pointer, - ); - return result == nullptr ? null : SubscriptionHandle._(result); - } - - static void _stateChangeCallback(Object userdata, int state) { - final completer = userdata as CancellableCompleter; - if (!completer.isCancelled) { - completer.complete(SubscriptionSetState.values[state]); - } - } - - Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen, - [CancellationToken? cancellationToken]) { - final completer = CancellableCompleter(cancellationToken); - if (!completer.isCancelled) { - final callback = Pointer.fromFunction(_stateChangeCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - _realmLib.realm_sync_on_subscription_set_state_change_async(subscriptions.handle._pointer, notifyWhen.index, - _realmLib.addresses.realm_dart_sync_on_subscription_state_changed_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - } - return completer.future; - } - - int subscriptionSetGetVersion(SubscriptionSet subscriptions) { - return _realmLib.realm_sync_subscription_set_version(subscriptions.handle._pointer); - } - - SubscriptionSetState subscriptionSetGetState(SubscriptionSet subscriptions) { - return SubscriptionSetState.values[_realmLib.realm_sync_subscription_set_state(subscriptions.handle._pointer)]; - } - - MutableSubscriptionSetHandle subscriptionSetMakeMutable(SubscriptionSet subscriptions) { - return MutableSubscriptionSetHandle._( - _realmLib.invokeGetPointer(() => _realmLib.realm_sync_make_subscription_set_mutable(subscriptions.handle._pointer)), subscriptions.realm.handle); - } - - SubscriptionSetHandle subscriptionSetCommit(MutableSubscriptionSet subscriptions) { - return SubscriptionSetHandle._( - _realmLib.invokeGetPointer(() => _realmLib.realm_sync_subscription_set_commit(subscriptions.handle._mutablePointer)), subscriptions.realm.handle); - } - - SubscriptionHandle insertOrAssignSubscription(MutableSubscriptionSet subscriptions, RealmResults results, String? name, bool update) { - if (!update) { - if (name != null && findSubscriptionByName(subscriptions, name) != null) { - throw RealmException('Duplicate subscription with name: $name'); + if (Platform.isWindows) { + return _getAppDirectoryFromPlugin(); } - } - return using((arena) { - final out_index = arena(); - final out_inserted = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign_results( - subscriptions.handle._mutablePointer, - results.handle._pointer, - name?.toCharPtr(arena) ?? nullptr, - out_index, - out_inserted, - )); - return subscriptionAt(subscriptions, out_index.value); - }); - } - - bool eraseSubscriptionById(MutableSubscriptionSet subscriptions, Subscription subscription) { - return using((arena) { - final out_found = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_id( - subscriptions.handle._mutablePointer, - subscription.id.toNative(arena), - out_found, - )); - return out_found.value; - }); - } - - bool eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { - return using((arena) { - final out_found = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_name( - subscriptions.handle._mutablePointer, - name.toCharPtr(arena), - out_found, - )); - return out_found.value; - }); - } - - bool eraseSubscriptionByResults(MutableSubscriptionSet subscriptions, RealmResults results) { - return using((arena) { - final out_found = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_results( - subscriptions.handle._mutablePointer, - results.handle._pointer, - out_found, - )); - return out_found.value; - }); - } - - void clearSubscriptionSet(MutableSubscriptionSet subscriptions) { - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_clear(subscriptions.handle._mutablePointer)); - } - - void refreshSubscriptionSet(SubscriptionSet subscriptions) { - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_refresh(subscriptions.handle._pointer)); - } - - static bool initial_data_callback(Pointer userdata, Pointer realmPtr) { - final realmHandle = RealmHandle._unowned(realmPtr); - try { - final LocalConfiguration config = userdata.toObject(); - final realm = RealmInternal.getUnowned(config, realmHandle); - config.initialDataCallback!(realm); - return true; - } catch (ex) { - _realmLib.realm_register_user_code_callback_error(ex.toPersistentHandle()); - } finally { - realmHandle.release(); - } - - return false; - } - - static bool should_compact_callback(Pointer userdata, int totalSize, int usedSize) { - final config = userdata.toObject(); - - if (config is LocalConfiguration) { - return config.shouldCompactCallback!(totalSize, usedSize); - } else if (config is FlexibleSyncConfiguration) { - return config.shouldCompactCallback!(totalSize, usedSize); - } - - return false; - } - - static bool migration_callback( - Pointer userdata, Pointer oldRealmHandle, Pointer newRealmHandle, Pointer schema) { - final oldHandle = RealmHandle._unowned(oldRealmHandle); - final newHandle = RealmHandle._unowned(newRealmHandle); - try { - final LocalConfiguration config = userdata.toObject(); - - final oldSchemaVersion = _realmLib.realm_get_schema_version(oldRealmHandle); - final oldConfig = Configuration.local([], path: config.path, isReadOnly: true, schemaVersion: oldSchemaVersion); - final oldRealm = RealmInternal.getUnowned(oldConfig, oldHandle, isInMigration: true); - - final newRealm = RealmInternal.getUnowned(config, newHandle, isInMigration: true); - - final migration = MigrationInternal.create(RealmInternal.getMigrationRealm(oldRealm), newRealm, SchemaHandle.unowned(schema)); - config.migrationCallback!(migration, oldSchemaVersion); - return true; - } catch (ex) { - _realmLib.realm_register_user_code_callback_error(ex.toPersistentHandle()); - } finally { - oldHandle.release(); - newHandle.release(); - } - - return false; - } - static void _syncErrorHandlerCallback(Object userdata, Pointer session, realm_sync_error error) { - final syncConfig = userdata as FlexibleSyncConfiguration; - // TODO: Take the app from the session instead of from syncConfig after fixing issue https://github.com/realm/realm-dart/issues/633 - final syncError = SyncErrorInternal.createSyncError(error.toSyncErrorDetails(), app: syncConfig.user.app); - - if (syncError is ClientResetError) { - syncConfig.clientResetHandler.onManualReset?.call(syncError); - return; - } - - syncConfig.syncErrorHandler(syncError); - } - - static void _guardSynchronousCallback(FutureOr Function() callback, Pointer unlockCallbackFunc) async { - Pointer user_error = nullptr; - try { - await callback(); - } catch (error) { - user_error = error.toPersistentHandle(); - } finally { - _realmLib.realm_dart_invoke_unlock_callback(user_error, unlockCallbackFunc); + throw UnsupportedError("Platform ${Platform.operatingSystem} is not supported"); + } catch (e) { + throw RealmException('Cannot get app directory. Error: $e'); } } - static void _syncBeforeResetCallback(Object userdata, Pointer realmHandle, Pointer unlockCallbackFunc) { - _guardSynchronousCallback(() async { - final syncConfig = userdata as FlexibleSyncConfiguration; - var beforeResetCallback = syncConfig.clientResetHandler.onBeforeReset!; - - final realm = RealmInternal.getUnowned(syncConfig, RealmHandle._unowned(realmHandle)); - try { - await beforeResetCallback(realm); - } finally { - realm.handle.release(); - } - }, unlockCallbackFunc); - } - - static void _syncAfterResetCallback(Object userdata, Pointer beforeHandle, Pointer afterReference, bool didRecover, - Pointer unlockCallbackFunc) { - _guardSynchronousCallback(() async { - final syncConfig = userdata as FlexibleSyncConfiguration; - final afterResetCallback = didRecover ? syncConfig.clientResetHandler.onAfterRecovery : syncConfig.clientResetHandler.onAfterDiscard; - - if (afterResetCallback == null) { - return; - } - - final beforeRealm = RealmInternal.getUnowned(syncConfig, RealmHandle._unowned(beforeHandle)); - final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_from_thread_safe_reference(afterReference, scheduler.handle._pointer)); - final afterRealm = RealmInternal.getUnowned(syncConfig, RealmHandle._unowned(realmPtr)); - + String getBundleId() { + readBundleId() { try { - return await afterResetCallback(beforeRealm, afterRealm); - } finally { - beforeRealm.handle.release(); - afterRealm.handle.release(); - } - }, unlockCallbackFunc); - } - - void raiseError(Session session, int errorCode, bool isFatal) { - using((arena) { - final message = "Simulated session error".toCharPtr(arena); - _realmLib.realm_sync_session_handle_error_for_testing(session.handle._pointer, errorCode, message, isFatal); - }); - } - - void realmDisableAutoRefreshForTesting(Realm realm) { - _realmLib.realm_set_auto_refresh(realm.handle._pointer, false); - } - - SchedulerHandle createScheduler(int isolateId, int sendPort) { - final schedulerPtr = _realmLib.realm_dart_create_scheduler(isolateId, sendPort); - return SchedulerHandle._(schedulerPtr); - } - - void invokeScheduler(int workQueue) { - final queuePointer = Pointer.fromAddress(workQueue); - _realmLib.realm_scheduler_perform_work(queuePointer); - } - - RealmHandle openRealm(Configuration config) { - final configHandle = _createConfig(config); - final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open(configHandle._pointer), "Error opening realm at path ${config.path}"); - return RealmHandle._(realmPtr); - } + if (!isFlutterPlatform || Platform.environment.containsKey('FLUTTER_TEST')) { + var pubspecPath = path.join(path.current, 'pubspec.yaml'); + var pubspecFile = File(pubspecPath); - RealmAsyncOpenTaskHandle createRealmAsyncOpenTask(FlexibleSyncConfiguration config) { - final configHandle = _createConfig(config); - final asyncOpenTaskPtr = - _realmLib.invokeGetPointer(() => _realmLib.realm_open_synchronized(configHandle._pointer), "Error opening realm at path ${config.path}"); - return RealmAsyncOpenTaskHandle._(asyncOpenTaskPtr); - } + if (pubspecFile.existsSync()) { + final pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); + return pubspec.name; + } + } - Future openRealmAsync(RealmAsyncOpenTaskHandle handle, CancellationToken? cancellationToken) { - final completer = CancellableCompleter(cancellationToken); - if (!completer.isCancelled) { - final callback = - Pointer.fromFunction realm, Pointer error)>(_openRealmAsyncCallback); - final userData = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - _realmLib.realm_async_open_task_start( - handle._pointer, - _realmLib.addresses.realm_dart_async_open_task_callback, - userData.cast(), - _realmLib.addresses.realm_dart_userdata_async_free, - ); - } - return completer.future; - } + if (Platform.isAndroid) { + return realmLib.realm_dart_get_bundle_id().cast().toDartString(); + } - static void _openRealmAsyncCallback(Object userData, Pointer realmSafePtr, Pointer error) { - return using((Arena arena) { - final completer = userData as CancellableCompleter; - if (completer.isCancelled) { - return; - } - if (error != nullptr) { - final err = arena(); - final lastError = _realmLib.realm_get_async_error(error, err) ? err.ref.toLastError() : null; - completer.completeError(RealmException("Failed to open realm: ${lastError?.message ?? 'Error details missing.'}")); - return; + final getBundleIdFunc = _pluginLib.lookupFunction Function(), Pointer Function()>("realm_dart_get_bundle_id"); + final bundleIdPtr = getBundleIdFunc(); + return bundleIdPtr.cast().toDartString(); + } on Exception catch (_) { + //Never fail on bundleId. Use fallback value. } - final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_from_thread_safe_reference(realmSafePtr, scheduler.handle._pointer)); - completer.complete(RealmHandle._(realmPtr)); - }); - } - - void cancelOpenRealmAsync(RealmAsyncOpenTaskHandle handle) { - _realmLib.realm_async_open_task_cancel(handle._pointer); - } - - RealmAsyncOpenTaskProgressNotificationTokenHandle realmAsyncOpenRegisterAsyncOpenProgressNotifier( - RealmAsyncOpenTaskHandle handle, RealmAsyncOpenProgressNotificationsController controller) { - final callback = Pointer.fromFunction(_syncProgressCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle._pointer); - final tokenPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_async_open_task_register_download_progress_notifier( - handle._pointer, - _realmLib.addresses.realm_dart_sync_progress_callback, - userdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - return RealmAsyncOpenTaskProgressNotificationTokenHandle._(tokenPtr); - } - - RealmSchema readSchema(Realm realm) { - return using((Arena arena) { - return _readSchema(realm, arena); - }); - } - - RealmSchema _readSchema(Realm realm, Arena arena, {int expectedSize = 10}) { - final classesPtr = arena(expectedSize); - final actualCount = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_get_class_keys(realm.handle._pointer, classesPtr, expectedSize, actualCount)); - if (expectedSize < actualCount.value) { - arena.free(classesPtr); - return _readSchema(realm, arena, expectedSize: actualCount.value); - } - - final schemas = []; - for (var i = 0; i < actualCount.value; i++) { - final classInfo = arena(); - final classKey = classesPtr.elementAt(i).value; - _realmLib.invokeGetBool(() => _realmLib.realm_get_class(realm.handle._pointer, classKey, classInfo)); - - final name = classInfo.ref.name.cast().toDartString(); - final baseType = ObjectType.values.firstWhere((element) => element.flags == classInfo.ref.flags, - orElse: () => throw RealmError('No object type found for flags ${classInfo.ref.flags}')); - final schema = - _getSchemaForClassKey(realm, classKey, name, baseType, arena, expectedSize: classInfo.ref.num_properties + classInfo.ref.num_computed_properties); - schemas.add(schema); - } - - return RealmSchema(schemas); - } - - SchemaObject _getSchemaForClassKey(Realm realm, int classKey, String name, ObjectType baseType, Arena arena, {int expectedSize = 10}) { - final actualCount = arena(); - final propertiesPtr = arena(expectedSize); - _realmLib.invokeGetBool(() => _realmLib.realm_get_class_properties(realm.handle._pointer, classKey, propertiesPtr, expectedSize, actualCount)); - - if (expectedSize < actualCount.value) { - // The supplied array was too small - resize it - arena.free(propertiesPtr); - return _getSchemaForClassKey(realm, classKey, name, baseType, arena, expectedSize: actualCount.value); - } - - final result = []; - for (var i = 0; i < actualCount.value; i++) { - final property = propertiesPtr.elementAt(i).ref.toSchemaProperty(); - result.add(property); - } - - late Type type; - switch (baseType) { - case ObjectType.realmObject: - type = RealmObject; - break; - case ObjectType.embeddedObject: - type = EmbeddedObject; - break; - case ObjectType.asymmetricObject: - type = AsymmetricObject; - break; - default: - throw RealmError('$baseType is not supported yet'); + //Fallback value + return "realm_bundle_id"; } - return SchemaObject(baseType, type, name, result); - } - - void deleteRealmFiles(String path) { - using((Arena arena) { - final realm_deleted = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_delete_files(path.toCharPtr(arena), realm_deleted), "Error deleting realm at path $path"); - }); + String bundleId = readBundleId(); + const salt = [82, 101, 97, 108, 109, 32, 105, 115, 32, 103, 114, 101, 97, 116]; + return base64Encode(sha256.convert([...salt, ...utf8.encode(bundleId)]).bytes); } - String getFilesPath() { - return _realmLib.realm_dart_get_files_path().cast().toRealmDartString()!; + String getDefaultBaseUrl() { + return realmLib.realm_app_get_default_base_url().cast().toRealmDartString()!; } String getDeviceName() { if (Platform.isAndroid || Platform.isIOS) { - return _realmLib.realm_dart_get_device_name().cast().toRealmDartString()!; + return realmLib.realm_dart_get_device_name().cast().toRealmDartString()!; } return ""; @@ -806,3108 +149,43 @@ class _RealmCore { String getDeviceVersion() { if (Platform.isAndroid || Platform.isIOS) { - return _realmLib.realm_dart_get_device_version().cast().toRealmDartString()!; + return realmLib.realm_dart_get_device_version().cast().toRealmDartString()!; } return ""; } String getRealmLibraryCpuArchitecture() { - return _realmLib.realm_get_library_cpu_arch().cast().toDartString(); - } - - void closeRealm(Realm realm) { - _realmLib.invokeGetBool(() => _realmLib.realm_close(realm.handle._pointer), "Realm close failed"); + return realmLib.realm_get_library_cpu_arch().cast().toDartString(); } - bool isRealmClosed(Realm realm) { - return _realmLib.realm_is_closed(realm.handle._pointer); - } + void loggerAttach() => realmLib.realm_dart_attach_logger(scheduler.nativePort); - void beginWrite(Realm realm) { - _realmLib.invokeGetBool(() => _realmLib.realm_begin_write(realm.handle._pointer), "Could not begin write"); - } + void loggerDetach() => realmLib.realm_dart_detach_logger(scheduler.nativePort); - void commitWrite(Realm realm) { - _realmLib.invokeGetBool(() => _realmLib.realm_commit(realm.handle._pointer), "Could not commit write"); + void logMessage(LogCategory category, LogLevel logLevel, String message) { + return using((arena) { + realmLib.realm_dart_log(logLevel.index, category.toString().toCharPtr(arena), message.toCharPtr(arena)); + }); } - Future beginWriteAsync(Realm realm, CancellationToken? ct) { - int? id; - final completer = CancellableCompleter(ct, onCancel: () { - if (id != null) { - realmCore._cancelAsync(realm, id!); - } + void setLogLevel(LogLevel level, {required LogCategory category}) { + using((arena) { + realmLib.realm_set_log_level_category(category.toString().toCharPtr(arena), level.index); }); - if (ct?.isCancelled != true) { - using((arena) { - final transaction_id = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_async_begin_write( - realm.handle._pointer, - Pointer.fromFunction(_completeAsyncBeginWrite), - completer.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - true, - transaction_id, - )); - id = transaction_id.value; - }); - } - return completer.future; } - Future commitWriteAsync(Realm realm, CancellationToken? ct) { - int? id; - final completer = CancellableCompleter(ct, onCancel: () { - if (id != null) { - realmCore._cancelAsync(realm, id!); - } - }); - if (ct?.isCancelled != true) { - using((arena) { - final transaction_id = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_async_commit( - realm.handle._pointer, - Pointer.fromFunction(_completeAsyncCommit), - completer.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - false, - transaction_id, - )); - id = transaction_id.value; - }); - } - return completer.future; - } - - bool _cancelAsync(Realm realm, int cancellationId) { - return using((Arena arena) { - final didCancel = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_async_cancel(realm.handle._pointer, cancellationId, didCancel)); - return didCancel.value; - }); - } - - static void _completeAsyncBeginWrite(Pointer userdata) { - final Completer completer = userdata.toObject(); - completer.complete(); - } - - static void _completeAsyncCommit(Pointer userdata, bool error, Pointer description) { - final Completer completer = userdata.toObject(); - if (error) { - completer.completeError(RealmException(description.cast().toDartString())); - } else { - completer.complete(); - } - } - - bool getIsWritable(Realm realm) { - return _realmLib.realm_is_writable(realm.handle._pointer); - } - - void rollbackWrite(Realm realm) { - _realmLib.invokeGetBool(() => _realmLib.realm_rollback(realm.handle._pointer), "Could not rollback write"); - } - - bool realmRefresh(Realm realm) { - return using((Arena arena) { - final did_refresh = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_refresh(realm.handle._pointer, did_refresh), "Could not refresh"); - return did_refresh.value; - }); - } - - Future realmRefreshAsync(Realm realm) async { - final completer = Completer(); - final callback = Pointer.fromFunction)>(_realmRefreshAsyncCallback); - Pointer completerPtr = _realmLib.realm_dart_object_to_persistent_handle(completer); - Pointer result = _realmLib.realm_add_realm_refresh_callback( - realm.handle._pointer, callback.cast(), completerPtr, _realmLib.addresses.realm_dart_delete_persistent_handle); - - if (result == nullptr) { - return Future.value(false); - } - - return completer.future; - } - - static void _realmRefreshAsyncCallback(Pointer userdata) { - if (userdata == nullptr) { - return; - } - - final completer = _realmLib.realm_dart_persistent_handle_to_object(userdata) as Completer; - completer.complete(true); - } - - RealmObjectMetadata getObjectMetadata(Realm realm, SchemaObject schema) { - return using((Arena arena) { - final found = arena(); - final classInfo = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_find_class(realm.handle._pointer, schema.name.toCharPtr(arena), found, classInfo), - "Error getting class ${schema.name} from realm at ${realm.config.path}"); - - if (!found.value) { - throwLastError("Class ${schema.name} not found in ${realm.config.path}"); - } - - final primaryKey = classInfo.ref.primary_key.cast().toRealmDartString(treatEmptyAsNull: true); - return RealmObjectMetadata(schema, classInfo.ref.key, _getPropertiesMetadata(realm, classInfo.ref.key, primaryKey, arena)); - }); - } - - Map getPropertiesMetadata(Realm realm, int classKey, String? primaryKeyName) { - return using((Arena arena) { - return _getPropertiesMetadata(realm, classKey, primaryKeyName, arena); - }); - } - - Map _getPropertiesMetadata(Realm realm, int classKey, String? primaryKeyName, Arena arena) { - final propertyCountPtr = arena(); - _realmLib.invokeGetBool( - () => _realmLib.realm_get_property_keys(realm.handle._pointer, classKey, nullptr, 0, propertyCountPtr), "Error getting property count"); - - var propertyCount = propertyCountPtr.value; - final propertiesPtr = arena(propertyCount); - _realmLib.invokeGetBool(() => _realmLib.realm_get_class_properties(realm.handle._pointer, classKey, propertiesPtr, propertyCount, propertyCountPtr), - "Error getting class properties."); - - propertyCount = propertyCountPtr.value; - Map result = {}; - for (var i = 0; i < propertyCount; i++) { - final property = propertiesPtr.elementAt(i); - final propertyName = property.ref.name.cast().toRealmDartString()!; - final objectType = property.ref.link_target.cast().toRealmDartString(treatEmptyAsNull: true); - final linkOriginProperty = property.ref.link_origin_property_name.cast().toRealmDartString(treatEmptyAsNull: true); - final isNullable = property.ref.flags & realm_property_flags.RLM_PROPERTY_NULLABLE != 0; - final isPrimaryKey = propertyName == primaryKeyName; - final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, linkOriginProperty, RealmPropertyType.values.elementAt(property.ref.type), - isNullable, isPrimaryKey, RealmCollectionType.values.elementAt(property.ref.collection_type)); - result[propertyName] = propertyMeta; - } - return result; - } - - RealmObjectHandle createRealmObject(Realm realm, int classKey) { - final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_object_create(realm.handle._pointer, classKey)); - return RealmObjectHandle._(realmPtr, realm.handle); - } - - RealmObjectHandle createEmbeddedObject(RealmObjectBase obj, int propertyKey) { - final objectPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_set_embedded(obj.handle._pointer, propertyKey)); - return RealmObjectHandle._(objectPtr, obj.realm.handle); - } - - Tuple getEmbeddedParent(EmbeddedObject obj) { - return using((Arena arena) { - final parentPtr = arena>(); - final classKeyPtr = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_object_get_parent(obj.handle._pointer, parentPtr, classKeyPtr)); - - final handle = RealmObjectHandle._(parentPtr.value, obj.realm.handle); - - return Tuple(handle, classKeyPtr.value); - }); - } - - int getClassKey(RealmObjectHandle handle) { - return _realmLib.realm_object_get_table(handle._pointer); - } - - RealmObjectHandle getOrCreateRealmObjectWithPrimaryKey(Realm realm, int classKey, Object? primaryKey) { - return using((Arena arena) { - final realm_value = _toRealmValue(primaryKey, arena); - final didCreate = arena(); - final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_object_get_or_create_with_primary_key( - realm.handle._pointer, - classKey, - realm_value.ref, - didCreate, - )); - return RealmObjectHandle._(realmPtr, realm.handle); - }); - } - - RealmObjectHandle createRealmObjectWithPrimaryKey(Realm realm, int classKey, Object? primaryKey) { - return using((Arena arena) { - final realm_value = _toRealmValue(primaryKey, arena); - final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_object_create_with_primary_key(realm.handle._pointer, classKey, realm_value.ref)); - return RealmObjectHandle._(realmPtr, realm.handle); - }); - } - - Object? getProperty(RealmObjectBase object, int propertyKey) { - return using((Arena arena) { - final realm_value = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_get_value(object.handle._pointer, propertyKey, realm_value)); - return realm_value.toDartValue(object.realm, () => _realmLib.realm_get_list(object.handle._pointer, propertyKey), - () => _realmLib.realm_get_dictionary(object.handle._pointer, propertyKey)); - }); - } - - void setProperty(RealmObjectBase object, int propertyKey, Object? value, bool isDefault) { - using((Arena arena) { - final realm_value = _toRealmValue(value, arena); - _realmLib.invokeGetBool(() => _realmLib.realm_set_value(object.handle._pointer, propertyKey, realm_value.ref, isDefault)); - }); - } - - void objectSetCollection(RealmObjectBase object, int propertyKey, RealmValue value) { - _createCollection(object.realm, value, () => _realmLib.realm_set_list(object.handle._pointer, propertyKey), - () => _realmLib.realm_set_dictionary(object.handle._pointer, propertyKey)); - } - - String objectToString(RealmObjectBase object) { - return _realmLib.realm_object_to_string(object.handle._pointer).cast().toRealmDartString(freeRealmMemory: true)!; - } - - // For debugging - // ignore: unused_element - int get _threadId => _realmLib.realm_dart_get_thread_id(); - - RealmObjectHandle? find(Realm realm, int classKey, Object? primaryKey) { - return using((Arena arena) { - final realm_value = _toRealmValue(primaryKey, arena); - final pointer = _realmLib.realm_object_find_with_primary_key(realm.handle._pointer, classKey, realm_value.ref, nullptr); - if (pointer == nullptr) { - return null; - } - - return RealmObjectHandle._(pointer, realm.handle); - }); - } - - RealmObjectHandle? findExisting(Realm realm, int classKey, RealmObjectHandle other) { - final key = _realmLib.realm_object_get_key(other._pointer); - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_object(realm.handle._pointer, classKey, key)); - return RealmObjectHandle._(pointer, realm.handle); - } - - void renameProperty(Realm realm, String objectType, String oldName, String newName, SchemaHandle schema) { - using((Arena arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_schema_rename_property( - realm.handle._pointer, schema._pointer, objectType.toCharPtr(arena), oldName.toCharPtr(arena), newName.toCharPtr(arena))); - }); - } - - bool deleteType(Realm realm, String objectType) { - return using((Arena arena) { - final deletedPtr = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_remove_table(realm.handle._pointer, objectType.toCharPtr(arena), deletedPtr)); - return deletedPtr.value; - }); - } - - void deleteRealmObject(RealmObjectBase object) { - _realmLib.invokeGetBool(() => _realmLib.realm_object_delete(object.handle._pointer)); - } - - RealmResultsHandle findAll(Realm realm, int classKey) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_object_find_all(realm.handle._pointer, classKey)); - return RealmResultsHandle._(pointer, realm.handle); - } - - RealmResultsHandle queryClass(Realm realm, int classKey, String query, List args) { - return using((arena) { - final length = args.length; - final argsPointer = arena(length); - for (var i = 0; i < length; ++i) { - _intoRealmQueryArg(args[i], argsPointer.elementAt(i), arena); - } - final queryHandle = _RealmQueryHandle._( - _realmLib.invokeGetPointer( - () => _realmLib.realm_query_parse( - realm.handle._pointer, - classKey, - query.toCharPtr(arena), - length, - argsPointer, - ), - ), - realm.handle); - return _queryFindAll(queryHandle); - }); - } - - RealmResultsHandle queryResults(RealmResults target, String query, List args) { - return using((arena) { - final length = args.length; - final argsPointer = arena(length); - for (var i = 0; i < length; ++i) { - _intoRealmQueryArg(args[i], argsPointer.elementAt(i), arena); - } - final queryHandle = _RealmQueryHandle._( - _realmLib.invokeGetPointer( - () => _realmLib.realm_query_parse_for_results( - target.handle._pointer, - query.toCharPtr(arena), - length, - argsPointer, - ), - ), - target.realm.handle); - return _queryFindAll(queryHandle); - }); - } - - RealmResultsHandle _queryFindAll(_RealmQueryHandle queryHandle) { - try { - final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_query_find_all(queryHandle._pointer)); - return RealmResultsHandle._(resultsPointer, queryHandle._root); - } finally { - queryHandle.release(); - } - } - - RealmResultsHandle queryList(RealmList target, String query, List args) { - return using((arena) { - final length = args.length; - final argsPointer = arena(length); - for (var i = 0; i < length; ++i) { - _intoRealmQueryArg(args[i], argsPointer.elementAt(i), arena); - } - final queryHandle = _RealmQueryHandle._( - _realmLib.invokeGetPointer( - () => _realmLib.realm_query_parse_for_list( - target.handle._pointer, - query.toCharPtr(arena), - length, - argsPointer, - ), - ), - target.realm.handle); - return _queryFindAll(queryHandle); - }); - } - - RealmResultsHandle querySet(RealmSet target, String query, List args) { - return using((arena) { - final length = args.length; - final argsPointer = arena(length); - for (var i = 0; i < length; ++i) { - _intoRealmQueryArg(args[i], argsPointer.elementAt(i), arena); - } - final queryHandle = _RealmQueryHandle._( - _realmLib.invokeGetPointer( - () => _realmLib.realm_query_parse_for_set( - target.handle._pointer, - query.toCharPtr(arena), - length, - argsPointer, - ), - ), - target.realm.handle); - return _queryFindAll(queryHandle); - }); - } - - RealmResultsHandle queryMap(ManagedRealmMap target, String query, List args) { - return using((arena) { - final length = args.length; - final argsPointer = arena(length); - for (var i = 0; i < length; ++i) { - _intoRealmQueryArg(args[i], argsPointer.elementAt(i), arena); - } - - final results = mapGetValues(target); - final queryHandle = _RealmQueryHandle._( - _realmLib.invokeGetPointer( - () => _realmLib.realm_query_parse_for_results( - results._pointer, - query.toCharPtr(arena), - length, - argsPointer, - ), - ), - target.realm.handle); - return _queryFindAll(queryHandle); - }); - } - - RealmResultsHandle resultsFromList(RealmList list) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_list_to_results(list.handle._pointer)); - return RealmResultsHandle._(pointer, list.realm.handle); - } - - RealmResultsHandle resultsFromSet(RealmSet set) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_set_to_results(set.handle._pointer)); - return RealmResultsHandle._(pointer, set.realm.handle); - } - - Object? resultsGetElementAt(RealmResults results, int index) { - return using((Arena arena) { - final realm_value = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_results_get(results.handle._pointer, index, realm_value)); - return realm_value.toDartValue(results.realm, () => _realmLib.realm_results_get_list(results.handle._pointer, index), - () => _realmLib.realm_results_get_dictionary(results.handle._pointer, index)); - }); - } - - int resultsFind(RealmResults results, Object? value) { - return using((Arena arena) { - final out_index = arena(); - final out_found = arena(); - - // TODO: how should this behave for collections - final realm_value = _toRealmValue(value, arena); - _realmLib.invokeGetBool( - () => _realmLib.realm_results_find( - results.handle._pointer, - realm_value, - out_index, - out_found, - ), - ); - return out_found.value ? out_index.value : -1; - }); - } - - RealmObjectHandle resultsGetObjectAt(RealmResults results, int index) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_get_object(results.handle._pointer, index)); - return RealmObjectHandle._(pointer, results.realm.handle); - } - - int getResultsCount(RealmResults results) { - return using((Arena arena) { - final countPtr = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_results_count(results.handle._pointer, countPtr)); - return countPtr.value; - }); - } - - bool resultsIsValid(RealmResults results) { - return using((arena) { - final is_valid = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_results_is_valid(results.handle._pointer, is_valid)); - return is_valid.value; - }); - } - - CollectionChanges getCollectionChanges(RealmCollectionChangesHandle changes) { - return using((arena) { - final out_num_deletions = arena(); - final out_num_insertions = arena(); - final out_num_modifications = arena(); - final out_num_moves = arena(); - final out_collection_cleared = arena(); - final out_collection_was_deleted = arena(); - _realmLib.realm_collection_changes_get_num_changes( - changes._pointer, - out_num_deletions, - out_num_insertions, - out_num_modifications, - out_num_moves, - out_collection_cleared, - out_collection_was_deleted, - ); - - final deletionsCount = out_num_deletions != nullptr ? out_num_deletions.value : 0; - final insertionCount = out_num_insertions != nullptr ? out_num_insertions.value : 0; - final modificationCount = out_num_modifications != nullptr ? out_num_modifications.value : 0; - var moveCount = out_num_moves != nullptr ? out_num_moves.value : 0; - - final out_deletion_indexes = arena(deletionsCount); - final out_insertion_indexes = arena(insertionCount); - final out_modification_indexes = arena(modificationCount); - final out_modification_indexes_after = arena(modificationCount); - final out_moves = arena(moveCount); - - _realmLib.realm_collection_changes_get_changes( - changes._pointer, - out_deletion_indexes, - deletionsCount, - out_insertion_indexes, - insertionCount, - out_modification_indexes, - modificationCount, - out_modification_indexes_after, - modificationCount, - out_moves, - moveCount, - ); - - var elementZero = out_moves.elementAt(0); - List moves = List.filled(moveCount, Move(elementZero.ref.from, elementZero.ref.to)); - for (var i = 1; i < moveCount; i++) { - final movePtr = out_moves.elementAt(i); - moves[i] = Move(movePtr.ref.from, movePtr.ref.to); - } - - return CollectionChanges( - out_deletion_indexes.toIntList(deletionsCount), - out_insertion_indexes.toIntList(insertionCount), - out_modification_indexes.toIntList(modificationCount), - out_modification_indexes_after.toIntList(modificationCount), - moves, - out_collection_cleared.value, - out_collection_was_deleted.value, - ); - }); - } - - MapChanges getMapChanges(RealmMapChangesHandle changes) { - return using((arena) { - final out_num_deletions = arena(); - final out_num_insertions = arena(); - final out_num_modifications = arena(); - final out_collection_was_deleted = arena(); - _realmLib.realm_dictionary_get_changes( - changes._pointer, - out_num_deletions, - out_num_insertions, - out_num_modifications, - out_collection_was_deleted, - ); - - final deletionsCount = out_num_deletions != nullptr ? out_num_deletions.value : 0; - final insertionCount = out_num_insertions != nullptr ? out_num_insertions.value : 0; - final modificationCount = out_num_modifications != nullptr ? out_num_modifications.value : 0; - - final out_deletion_indexes = arena(deletionsCount); - final out_insertion_indexes = arena(insertionCount); - final out_modification_indexes = arena(modificationCount); - final out_collection_was_cleared = arena(); - - _realmLib.realm_dictionary_get_changed_keys( - changes._pointer, - out_deletion_indexes, - out_num_deletions, - out_insertion_indexes, - out_num_insertions, - out_modification_indexes, - out_num_modifications, - out_collection_was_cleared, - ); - - return MapChanges(out_deletion_indexes.toStringList(deletionsCount), out_insertion_indexes.toStringList(insertionCount), - out_modification_indexes.toStringList(modificationCount), out_collection_was_cleared.value, out_collection_was_deleted.value); - }); - } - - _RealmLinkHandle _getObjectAsLink(RealmObjectBase object) { - final realmLink = _realmLib.realm_object_as_link(object.handle._pointer); - return _RealmLinkHandle._(realmLink); - } - - RealmObjectHandle _getObject(Realm realm, int classKey, int objectKey) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_object(realm.handle._pointer, classKey, objectKey)); - return RealmObjectHandle._(pointer, realm.handle); - } - - RealmListHandle getListProperty(RealmObjectBase object, int propertyKey) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_list(object.handle._pointer, propertyKey)); - return RealmListHandle._(pointer, object.realm.handle); - } - - RealmResultsHandle getBacklinks(RealmObjectBase object, int sourceTableKey, int propertyKey) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_backlinks(object.handle._pointer, sourceTableKey, propertyKey)); - return RealmResultsHandle._(pointer, object.realm.handle); - } - - int getListSize(RealmListHandle handle) { - return using((Arena arena) { - final size = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_list_size(handle._pointer, size)); - return size.value; - }); - } - - Object? listGetElementAt(RealmList list, int index) { - return using((Arena arena) { - final realm_value = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_list_get(list.handle._pointer, index, realm_value)); - return realm_value.toDartValue( - list.realm, () => _realmLib.realm_list_get_list(list.handle._pointer, index), () => _realmLib.realm_list_get_dictionary(list.handle._pointer, index)); - }); - } - - void listAddElementAt(RealmListHandle handle, int index, Object? value, bool insert) { - using((Arena arena) { - final realm_value = _toRealmValue(value, arena); - _realmLib.invokeGetBool(() => (insert ? _realmLib.realm_list_insert : _realmLib.realm_list_set)(handle._pointer, index, realm_value.ref)); - }); - } - - void listAddCollectionAt(RealmListHandle handle, Realm realm, int index, RealmValue value, bool insert) { - _createCollection(realm, value, () => (insert ? _realmLib.realm_list_insert_list : _realmLib.realm_list_set_list)(handle._pointer, index), - () => (insert ? _realmLib.realm_list_insert_dictionary : _realmLib.realm_list_set_dictionary)(handle._pointer, index)); - } - - RealmObjectHandle listSetEmbeddedObjectAt(Realm realm, RealmListHandle handle, int index) { - final ptr = _realmLib.invokeGetPointer(() => _realmLib.realm_list_set_embedded(handle._pointer, index)); - return RealmObjectHandle._(ptr, realm.handle); - } - - RealmObjectHandle listInsertEmbeddedObjectAt(Realm realm, RealmListHandle handle, int index) { - final ptr = _realmLib.invokeGetPointer(() => _realmLib.realm_list_insert_embedded(handle._pointer, index)); - return RealmObjectHandle._(ptr, realm.handle); - } - - void listRemoveElementAt(RealmListHandle handle, int index) { - _realmLib.invokeGetBool(() => _realmLib.realm_list_erase(handle._pointer, index)); - } - - void listMoveElement(RealmListHandle handle, int from, int to) { - _realmLib.invokeGetBool(() => _realmLib.realm_list_move(handle._pointer, from, to)); - } - - void listDeleteAll(RealmList list) { - _realmLib.invokeGetBool(() => _realmLib.realm_list_remove_all(list.handle._pointer)); - } - - int listFind(RealmList list, Object? value) { - return using((Arena arena) { - final out_index = arena(); - final out_found = arena(); - - // TODO: how should this behave for collections - final realm_value = _toRealmValue(value, arena); - _realmLib.invokeGetBool( - () => _realmLib.realm_list_find( - list.handle._pointer, - realm_value, - out_index, - out_found, - ), - ); - return out_found.value ? out_index.value : -1; - }); - } - - void resultsDeleteAll(RealmResults results) { - _realmLib.invokeGetBool(() => _realmLib.realm_results_delete_all(results.handle._pointer)); - } - - void listClear(RealmListHandle listHandle) { - _realmLib.invokeGetBool(() => _realmLib.realm_list_clear(listHandle._pointer)); - } - - RealmSetHandle getSetProperty(RealmObjectBase object, int propertyKey) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_set(object.handle._pointer, propertyKey)); - return RealmSetHandle._(pointer, object.realm.handle); - } - - bool realmSetInsert(RealmSetHandle handle, Object? value) { - return using((Arena arena) { - final realm_value = _toRealmValue(value, arena); - final out_index = arena(); - final out_inserted = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_set_insert(handle._pointer, realm_value.ref, out_index, out_inserted)); - return out_inserted.value; - }); - } - - Object? realmSetGetElementAt(RealmSet realmSet, int index) { - return using((Arena arena) { - final realm_value = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_set_get(realmSet.handle._pointer, index, realm_value)); - final result = realm_value.toDartValue( - realmSet.realm, () => throw RealmException('Sets cannot contain collections'), () => throw RealmException('Sets cannot contain collections')); - return result; - }); - } - - bool realmSetFind(RealmSet realmSet, Object? value) { - return using((Arena arena) { - // TODO: how should this behave for collections - final realm_value = _toRealmValue(value, arena); - final out_index = arena(); - final out_found = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_set_find(realmSet.handle._pointer, realm_value.ref, out_index, out_found)); - return out_found.value; - }); - } - - bool realmSetErase(RealmSet realmSet, Object? value) { - return using((Arena arena) { - // TODO: do we support sets containing mixed collections - final realm_value = _toRealmValue(value, arena); - final out_erased = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_set_erase(realmSet.handle._pointer, realm_value.ref, out_erased)); - return out_erased.value; - }); - } - - void realmSetClear(RealmSetHandle handle) { - _realmLib.invokeGetBool(() => _realmLib.realm_set_clear(handle._pointer)); - } - - int realmSetSize(RealmSet realmSet) { - return using((Arena arena) { - final out_size = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_set_size(realmSet.handle._pointer, out_size)); - return out_size.value; - }); - } - - bool realmSetIsValid(RealmSet realmSet) { - return _realmLib.realm_set_is_valid(realmSet.handle._pointer); - } - - void realmSetRemoveAll(RealmSet realmSet) { - _realmLib.invokeGetBool(() => _realmLib.realm_set_remove_all(realmSet.handle._pointer)); - } - - RealmNotificationTokenHandle subscribeSetNotifications(RealmSet realmSet, NotificationsController controller) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_set_add_notification_callback( - realmSet.handle._pointer, - controller.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - nullptr, - Pointer.fromFunction(collection_change_callback), - )); - - return RealmNotificationTokenHandle._(pointer, realmSet.realm.handle); - } - - int mapGetSize(RealmMapHandle handle) { - return using((Arena arena) { - final size = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_size(handle._pointer, size)); - return size.value; - }); - } - - bool mapRemoveKey(RealmMapHandle handle, String key) { - return using((Arena arena) { - final keyValue = _toRealmValue(key, arena); - final out_erased = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_erase(handle._pointer, keyValue.ref, out_erased)); - return out_erased.value; - }); - } - - Object? mapGetElement(RealmMap map, String key) { - return using((Arena arena) { - final realm_value = arena(); - final key_value = _toRealmValue(key, arena); - final out_found = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_find(map.handle._pointer, key_value.ref, realm_value, out_found)); - if (out_found.value) { - return realm_value.toDartValue(map.realm, () => _realmLib.realm_dictionary_get_list(map.handle._pointer, key_value.ref), - () => _realmLib.realm_dictionary_get_dictionary(map.handle._pointer, key_value.ref)); - } - - return null; - }); - } - - bool mapIsValid(RealmMap map) { - return _realmLib.realm_dictionary_is_valid(map.handle._pointer); - } - - void mapClear(RealmMapHandle mapHandle) { - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_clear(mapHandle._pointer)); - } - - RealmResultsHandle mapGetKeys(ManagedRealmMap map) { - return using((Arena arena) { - final out_size = arena(); - final out_keys = arena>(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_get_keys(map.handle._pointer, out_size, out_keys)); - return RealmResultsHandle._(out_keys.value, map.realm.handle); - }); - } - - RealmResultsHandle mapGetValues(ManagedRealmMap map) { - final result = _realmLib.invokeGetPointer(() => _realmLib.realm_dictionary_to_results(map.handle._pointer)); - return RealmResultsHandle._(result, map.realm.handle); - } - - bool mapContainsKey(ManagedRealmMap map, String key) { - return using((Arena arena) { - final key_value = _toRealmValue(key, arena); - final out_found = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_contains_key(map.handle._pointer, key_value.ref, out_found)); - return out_found.value; - }); - } - - bool mapContainsValue(ManagedRealmMap map, Object? value) { - return using((Arena arena) { - // TODO: how should this behave for collections - final value_value = _toRealmValue(value, arena); - final out_index = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_contains_value(map.handle._pointer, value_value.ref, out_index)); - return out_index.value > -1; - }); - } - - RealmObjectHandle mapInsertEmbeddedObject(Realm realm, RealmMapHandle handle, String key) { - return using((Arena arena) { - final realm_value = _toRealmValue(key, arena); - final ptr = _realmLib.invokeGetPointer(() => _realmLib.realm_dictionary_insert_embedded(handle._pointer, realm_value.ref)); - return RealmObjectHandle._(ptr, realm.handle); - }); - } - - void mapInsertValue(RealmMapHandle handle, String key, Object? value) { - using((Arena arena) { - final key_value = _toRealmValue(key, arena); - final value_value = _toRealmValue(value, arena); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_insert(handle._pointer, key_value.ref, value_value.ref, nullptr, nullptr)); - }); - } - - void mapInsertCollection(RealmMapHandle handle, Realm realm, String key, RealmValue value) { - using((Arena arena) { - final key_value = _toRealmValue(key, arena); - _createCollection(realm, value, () => _realmLib.realm_dictionary_insert_list(handle._pointer, key_value.ref), - () => _realmLib.realm_dictionary_insert_dictionary(handle._pointer, key_value.ref)); - }); - } - - RealmMapHandle getMapProperty(RealmObjectBase object, int propertyKey) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_get_dictionary(object.handle._pointer, propertyKey)); - return RealmMapHandle._(pointer, object.realm.handle); - } - - bool _equals(HandleBase first, HandleBase second) { - return _realmLib.realm_equals(first._pointer.cast(), second._pointer.cast()); - } - - bool objectEquals(RealmObjectBase first, RealmObjectBase second) => _equals(first.handle, second.handle); - bool realmEquals(Realm first, Realm second) => _equals(first.handle, second.handle); - bool userEquals(User first, User second) => _equals(first.handle, second.handle); - bool subscriptionEquals(Subscription first, Subscription second) => _equals(first.handle, second.handle); - - int objectGetHashCode(RealmObjectBase value) { - final link = realmCore._getObjectAsLink(value); - - var hashCode = -986587137; - hashCode = (hashCode * -1521134295) + link.classKey; - hashCode = (hashCode * -1521134295) + link.targetKey; - return hashCode; - } - - RealmResultsHandle resultsSnapshot(RealmResults results) { - final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_snapshot(results.handle._pointer)); - return RealmResultsHandle._(resultsPointer, results.realm.handle); - } - - bool objectIsValid(RealmObjectBase object) { - return _realmLib.realm_object_is_valid(object.handle._pointer); - } - - bool listIsValid(RealmList list) { - return _realmLib.realm_list_is_valid(list.handle._pointer); - } - - static void collection_change_callback(Pointer userdata, Pointer data) { - final NotificationsController controller = userdata.toObject(); - - if (data == nullptr) { - controller.onError(RealmError("Invalid notifications data received")); - return; - } - - try { - final clonedData = _realmLib.realm_clone(data.cast()); - if (clonedData == nullptr) { - controller.onError(RealmError("Error while cloning notifications data")); - return; - } - - final changesHandle = RealmCollectionChangesHandle._(clonedData.cast()); - controller.onChanges(changesHandle); - } catch (e) { - controller.onError(RealmError("Error handling change notifications. Error: $e")); - } - } - - static void object_change_callback(Pointer userdata, Pointer data) { - final NotificationsController controller = userdata.toObject(); - - if (data == nullptr) { - controller.onError(RealmError("Invalid notifications data received")); - return; - } - - try { - final clonedData = _realmLib.realm_clone(data.cast()); - if (clonedData == nullptr) { - controller.onError(RealmError("Error while cloning notifications data")); - return; - } - - final changesHandle = RealmObjectChangesHandle._(clonedData.cast()); - controller.onChanges(changesHandle); - } catch (e) { - controller.onError(RealmError("Error handling change notifications. Error: $e")); - } - } - - static void map_change_callback(Pointer userdata, Pointer data) { - final NotificationsController controller = userdata.toObject(); - - if (data == nullptr) { - controller.onError(RealmError("Invalid notifications data received")); - return; - } - - try { - final clonedData = _realmLib.realm_clone(data.cast()); - if (clonedData == nullptr) { - controller.onError(RealmError("Error while cloning notifications data")); - return; - } - - final changesHandle = RealmMapChangesHandle._(clonedData.cast()); - controller.onChanges(changesHandle); - } catch (e) { - controller.onError(RealmError("Error handling change notifications. Error: $e")); - } - } - - static void user_change_callback(Object userdata, int data) { - final controller = userdata as UserNotificationsController; - - controller.onUserChanged(); - } - - static void schema_change_callback(Pointer userdata, Pointer data) { - final Realm realm = userdata.toObject(); - try { - realm.updateSchema(); - } catch (e) { - Realm.logger.log(LogLevel.error, 'Failed to update Realm schema: $e'); - } - } - - RealmNotificationTokenHandle subscribeResultsNotifications(RealmResults results, NotificationsController controller) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_add_notification_callback( - results.handle._pointer, - controller.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - nullptr, - Pointer.fromFunction(collection_change_callback), - )); - - return RealmNotificationTokenHandle._(pointer, results.realm.handle); - } - - RealmNotificationTokenHandle subscribeListNotifications(RealmList list, NotificationsController controller) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_list_add_notification_callback( - list.handle._pointer, - controller.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - nullptr, - Pointer.fromFunction(collection_change_callback), - )); - - return RealmNotificationTokenHandle._(pointer, list.realm.handle); - } - - RealmNotificationTokenHandle subscribeObjectNotifications(RealmObjectBase object, NotificationsController controller, [List? keyPaths]) { - return using((Arena arena) { - final kpNative = buildAndVerifyKeyPath(object, keyPaths); - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_object_add_notification_callback( - object.handle._pointer, - controller.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - kpNative, - Pointer.fromFunction(object_change_callback), - )); - - return RealmNotificationTokenHandle._(pointer, object.realm.handle); - }); - } - - Pointer buildAndVerifyKeyPath(RealmObjectBase object, [List? keyPaths]) { - return using((Arena arena) { - if (keyPaths == null) { - return nullptr; - } - - final length = keyPaths.length; - final keypathsNative = arena>(length); - final classKey = object.realm.metadata.getByName(object.objectSchema.name).classKey; - - for (int i = 0; i < length; i++) { - keypathsNative[i] = keyPaths[i].toCharPtr(arena); - } - - return _realmLib.invokeGetPointer(() => _realmLib.realm_create_key_path_array(object.realm.handle._pointer, classKey, length, keypathsNative)); - }); - } - - RealmNotificationTokenHandle subscribeMapNotifications(RealmMap map, NotificationsController controller) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_dictionary_add_notification_callback( - map.handle._pointer, - controller.toPersistentHandle(), - _realmLib.addresses.realm_dart_delete_persistent_handle, - nullptr, - Pointer.fromFunction(map_change_callback), - )); - - return RealmNotificationTokenHandle._(pointer, map.realm.handle); - } - - UserNotificationTokenHandle subscribeUserNotifications(UserNotificationsController controller) { - final callback = Pointer.fromFunction(user_change_callback); - final userdata = _realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle._pointer); - final notification_token = _realmLib.realm_sync_user_on_state_change_register_callback( - controller.user.handle._pointer, - _realmLib.addresses.realm_dart_user_change_callback, - userdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free, - ); - return UserNotificationTokenHandle._(notification_token); - } - - RealmCallbackTokenHandle subscribeForSchemaNotifications(Realm realm) { - final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_add_schema_changed_callback(realm.handle._pointer, - Pointer.fromFunction(schema_change_callback), realm.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle)); - - return RealmCallbackTokenHandle._(pointer, realm.handle); - } - - bool getObjectChangesIsDeleted(RealmObjectChangesHandle handle) { - return _realmLib.realm_object_changes_is_deleted(handle._pointer); - } - - List getObjectChangesProperties(RealmObjectChangesHandle handle) { - return using((arena) { - final count = _realmLib.realm_object_changes_get_num_modified_properties(handle._pointer); - - final out_modified = arena(count); - _realmLib.realm_object_changes_get_modified_properties(handle._pointer, out_modified, count); - - return out_modified.asTypedList(count).toList(); - }); - } - - AppConfigHandle _createAppConfig(AppConfiguration configuration, RealmHttpTransportHandle httpTransport) { - return using((arena) { - final app_id = configuration.appId.toCharPtr(arena); - final handle = AppConfigHandle._(_realmLib.realm_app_config_new(app_id, httpTransport._pointer)); - - _realmLib.realm_app_config_set_platform_version(handle._pointer, Platform.operatingSystemVersion.toCharPtr(arena)); - - _realmLib.realm_app_config_set_sdk(handle._pointer, 'Dart'.toCharPtr(arena)); - _realmLib.realm_app_config_set_sdk_version(handle._pointer, libraryVersion.toCharPtr(arena)); - - final deviceName = getDeviceName(); - _realmLib.realm_app_config_set_device_name(handle._pointer, deviceName.toCharPtr(arena)); - - final deviceVersion = getDeviceVersion(); - _realmLib.realm_app_config_set_device_version(handle._pointer, deviceVersion.toCharPtr(arena)); - - _realmLib.realm_app_config_set_framework_name(handle._pointer, (isFlutterPlatform ? 'Flutter' : 'Dart VM').toCharPtr(arena)); - _realmLib.realm_app_config_set_framework_version(handle._pointer, Platform.version.toCharPtr(arena)); - - _realmLib.realm_app_config_set_base_url(handle._pointer, configuration.baseUrl.toString().toCharPtr(arena)); - - _realmLib.realm_app_config_set_default_request_timeout(handle._pointer, configuration.defaultRequestTimeout.inMilliseconds); - - _realmLib.realm_app_config_set_bundle_id(handle._pointer, getBundleId().toCharPtr(arena)); - - _realmLib.realm_app_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toCharPtr(arena)); - _realmLib.realm_app_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); - _realmLib.realm_app_config_set_default_request_timeout(handle._pointer, configuration.defaultRequestTimeout.inMilliseconds); - if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { - _realmLib.realm_app_config_set_metadata_encryption_key(handle._pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); - } - - return handle; - }); - } - - RealmAppCredentialsHandle createAppCredentialsAnonymous(bool reuseCredentials) { - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_anonymous(reuseCredentials)); - } - - RealmAppCredentialsHandle createAppCredentialsEmailPassword(String email, String password) { - return using((arena) { - final emailPtr = email.toCharPtr(arena); - final passwordPtr = password.toRealmString(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_email_password(emailPtr, passwordPtr.ref)); - }); - } - - RealmAppCredentialsHandle createAppCredentialsJwt(String token) { - return using((arena) { - final tokenPtr = token.toCharPtr(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_jwt(tokenPtr)); - }); - } - - RealmAppCredentialsHandle createAppCredentialsApple(String idToken) { - return using((arena) { - final idTokenPtr = idToken.toCharPtr(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_apple(idTokenPtr)); - }); - } - - RealmAppCredentialsHandle createAppCredentialsFacebook(String accessToken) { - return using((arena) { - final accessTokenPtr = accessToken.toCharPtr(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_facebook(accessTokenPtr)); - }); - } - - RealmAppCredentialsHandle createAppCredentialsGoogleIdToken(String idToken) { - return using((arena) { - final idTokenPtr = idToken.toCharPtr(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_google_id_token(idTokenPtr)); - }); - } - - RealmAppCredentialsHandle createAppCredentialsGoogleAuthCode(String authCode) { - return using((arena) { - final authCodePtr = authCode.toCharPtr(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_google_auth_code(authCodePtr)); - }); - } - - RealmAppCredentialsHandle createAppCredentialsFunction(String payload) { - return using((arena) { - final payloadPtr = payload.toCharPtr(arena); - final credentialsPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_credentials_new_function(payloadPtr)); - return RealmAppCredentialsHandle._(credentialsPtr); - }); - } - - RealmAppCredentialsHandle createAppCredentialsApiKey(String key) { - return using((arena) { - final keyPtr = key.toCharPtr(arena); - return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_api_key(keyPtr)); - }); - } - - RealmHttpTransportHandle _createHttpTransport(HttpClient httpClient) { - final requestCallback = Pointer.fromFunction)>(_request_callback); - final requestCallbackUserdata = _realmLib.realm_dart_userdata_async_new(httpClient, requestCallback.cast(), scheduler.handle._pointer); - return RealmHttpTransportHandle._(_realmLib.realm_http_transport_new( - _realmLib.addresses.realm_dart_http_request_callback, - requestCallbackUserdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - } - - static void _request_callback(Object userData, realm_http_request request, Pointer request_context) { - // - // The request struct only survives until end-of-call, even though - // we explicitly call realm_http_transport_complete_request to - // mark request as completed later. - // - // Therefore we need to copy everything out of request before returning. - // We cannot clone request on the native side with realm_clone, - // since realm_http_request does not inherit from WrapC. - - final client = userData as HttpClient; - - client.connectionTimeout = Duration(milliseconds: request.timeout_ms); - - final url = Uri.parse(request.url.cast().toRealmDartString()!); - - final body = request.body.cast().toRealmDartString(length: request.body_size); - - final headers = {}; - for (int i = 0; i < request.num_headers; ++i) { - final header = request.headers[i]; - final name = header.name.cast().toRealmDartString()!; - final value = header.value.cast().toRealmDartString()!; - headers[name] = value; - } - - _request_callback_async(client, request.method, url, body, headers, request_context); - // The request struct dies here! - } - - static Future _request_callback_async( - HttpClient client, - int requestMethod, - Uri url, - String? body, - Map headers, - Pointer request_context, - ) async { - await using((arena) async { - final response_pointer = arena(); - final responseRef = response_pointer.ref; - final method = _HttpMethod.values[requestMethod]; - - try { - // Build request - late HttpClientRequest request; - - switch (method) { - case _HttpMethod.delete: - request = await client.deleteUrl(url); - break; - case _HttpMethod.put: - request = await client.putUrl(url); - break; - case _HttpMethod.patch: - request = await client.patchUrl(url); - break; - case _HttpMethod.post: - request = await client.postUrl(url); - break; - case _HttpMethod.get: - request = await client.getUrl(url); - break; - } - - for (final header in headers.entries) { - request.headers.add(header.key, header.value); - } - - if (body != null) { - request.add(utf8.encode(body)); - } - - Realm.logger.log(LogLevel.debug, "HTTP Transport: Executing ${method.name} $url"); - - final stopwatch = Stopwatch()..start(); - - // Do the call.. - final response = await request.close(); - - stopwatch.stop(); - Realm.logger.log(LogLevel.debug, "HTTP Transport: Executed ${method.name} $url: ${response.statusCode} in ${stopwatch.elapsedMilliseconds} ms"); - - final responseBody = await response.fold>([], (acc, l) => acc..addAll(l)); // gather response - - // Report back to core - responseRef.status_code = response.statusCode; - responseRef.body = responseBody.toCharPtr(arena); - responseRef.body_size = responseBody.length; - - int headerCnt = 0; - response.headers.forEach((name, values) { - headerCnt += values.length; - }); - - responseRef.headers = arena(headerCnt); - responseRef.num_headers = headerCnt; - - int index = 0; - response.headers.forEach((name, values) { - for (final value in values) { - final headerRef = responseRef.headers.elementAt(index).ref; - headerRef.name = name.toCharPtr(arena); - headerRef.value = value.toCharPtr(arena); - index++; - } - }); - - responseRef.custom_status_code = _CustomErrorCode.noError.code; - } on SocketException catch (socketEx) { - Realm.logger.log(LogLevel.warn, "HTTP Transport: SocketException executing ${method.name} $url: $socketEx"); - responseRef.custom_status_code = _CustomErrorCode.timeout.code; - } on HttpException catch (httpEx) { - Realm.logger.log(LogLevel.warn, "HTTP Transport: HttpException executing ${method.name} $url: $httpEx"); - responseRef.custom_status_code = _CustomErrorCode.unknownHttp.code; - } catch (ex) { - Realm.logger.log(LogLevel.error, "HTTP Transport: Exception executing ${method.name} $url: $ex"); - responseRef.custom_status_code = _CustomErrorCode.unknown.code; - } finally { - _realmLib.realm_http_transport_complete_request(request_context, response_pointer); - } - }); - } - - void logMessage(LogCategory category, LogLevel logLevel, String message) { - return using((arena) { - _realmLib.realm_dart_log(logLevel.index, category.toString().toCharPtr(arena), message.toCharPtr(arena)); - }); - } - - // TODO: - // We need a pure Dart equivalent of: - // `ServiceBinding.rootIsolateToken != null` - // to get rid of this hack. - final bool _isRootIsolate = Isolate.current.debugName == 'main'; - - static bool _firstTime = true; - AppHandle createApp(AppConfiguration configuration) { - // to avoid caching apps across hot restarts we clear the cache on the first - // call to createApp in the root isolate. - if (_firstTime && _isRootIsolate) { - _firstTime = false; - _realmLib.realm_clear_cached_apps(); - } - final httpTransportHandle = _createHttpTransport(configuration.httpClient); - final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); - final realmAppPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_create_cached(appConfigHandle._pointer)); - - return AppHandle._(realmAppPtr); - } - - String getDefaultBaseUrl() { - return _realmLib.realm_app_get_default_base_url().cast().toRealmDartString()!; - } - - AppHandle? getApp(String id, String? baseUrl) { - return using((arena) { - final out_app = arena>(); - _realmLib.invokeGetBool(() => _realmLib.realm_app_get_cached(id.toCharPtr(arena), baseUrl == null ? nullptr : baseUrl.toCharPtr(arena), out_app)); - return out_app.value == nullptr ? null : AppHandle._(out_app.value); - }); - } - - String appGetId(App app) { - return _realmLib.realm_app_get_app_id(app.handle._pointer).cast().toRealmDartString()!; - } - - static void _app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { - final Completer completer = userdata.toObject(); - - if (error != nullptr) { - completer.completeWithAppError(error); - return; - } - - user = _realmLib.realm_clone(user.cast()).cast(); // take an extra reference to the user object - if (user == nullptr) { - completer.completeError(RealmException("Error while cloning user object.")); - return; - } - - completer.complete(UserHandle._(user.cast())); - } - - Pointer _createAsyncUserCallbackUserdata(Completer completer) { - final callback = Pointer.fromFunction< - Void Function( - Pointer, - Pointer, - Pointer, - )>(_app_user_completion_callback); - - final userdata = _realmLib.realm_dart_userdata_async_new( - completer, - callback.cast(), - scheduler.handle._pointer, - ); - - return userdata.cast(); - } - - Future logIn(App app, Credentials credentials) async { - final completer = Completer(); - final userdata = _createAsyncUserCallbackUserdata(completer); - - _realmLib.invokeGetBool( - () => _realmLib.realm_app_log_in_with_credentials( - app.handle._pointer, - credentials.handle._pointer, - _realmLib.addresses.realm_dart_user_completion_callback, - userdata, - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Login failed"); - - return await completer.future; - } - - static void _void_completion_callback(Pointer userdata, Pointer error) { - final Completer completer = userdata.toObject(); - - if (error != nullptr) { - completer.completeWithAppError(error); - return; - } - - completer.complete(); - } - - Future appEmailPasswordRegisterUser(App app, String email, String password) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_register_email( - app.handle._pointer, - email.toCharPtr(arena), - password.toRealmString(arena).ref, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return completer.future; - } - - Future emailPasswordConfirmUser(App app, String token, String tokenId) async { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_confirm_user( - app.handle._pointer, - token.toCharPtr(arena), - tokenId.toCharPtr(arena), - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return await completer.future; - } - - Future emailPasswordResendUserConfirmation(App app, String email) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_resend_confirmation_email( - app.handle._pointer, - email.toCharPtr(arena), - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return completer.future; - } - - Future emailPasswordCompleteResetPassword(App app, String password, String token, String tokenId) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_reset_password( - app.handle._pointer, - password.toRealmString(arena).ref, - token.toCharPtr(arena), - tokenId.toCharPtr(arena), - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return completer.future; - } - - Future emailPasswordResetPassword(App app, String email) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_send_reset_password_email( - app.handle._pointer, - email.toCharPtr(arena), - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return completer.future; - } - - Future emailPasswordCallResetPasswordFunction(App app, String email, String password, String? argsAsJSON) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_call_reset_password_function( - app.handle._pointer, - email.toCharPtr(arena), - password.toRealmString(arena).ref, - argsAsJSON != null ? argsAsJSON.toCharPtr(arena) : nullptr, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return completer.future; - } - - Future emailPasswordRetryCustomConfirmationFunction(App app, String email) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_app_email_password_provider_client_retry_custom_confirmation( - app.handle._pointer, - email.toCharPtr(arena), - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - }); - return completer.future; - } - - UserHandle? getCurrentUser(AppHandle appHandle) { - final userPtr = _realmLib.realm_app_get_current_user(appHandle._pointer); - if (userPtr == nullptr) { - return null; - } - return UserHandle._(userPtr); - } - - Future logOut(App application, User? user) { - final completer = Completer(); - if (user == null) { - _realmLib.invokeGetBool( - () => _realmLib.realm_app_log_out_current_user( - application.handle._pointer, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Logout failed"); - } else { - _realmLib.invokeGetBool( - () => _realmLib.realm_app_log_out( - application.handle._pointer, - user.handle._pointer, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Logout failed"); - } - return completer.future; - } - - void clearCachedApps() { - _realmLib.realm_clear_cached_apps(); - } - - List getUsers(App app) { - return using((arena) { - return _getUsers(app, arena); - }); - } - - List _getUsers(App app, Arena arena, {int expectedSize = 2}) { - final actualCount = arena(); - final usersPtr = arena>(expectedSize); - _realmLib.invokeGetBool(() => _realmLib.realm_app_get_all_users(app.handle._pointer, usersPtr, expectedSize, actualCount)); - - if (expectedSize < actualCount.value) { - // The supplied array was too small - resize it - arena.free(usersPtr); - return _getUsers(app, arena, expectedSize: actualCount.value); - } - - final result = []; - for (var i = 0; i < actualCount.value; i++) { - result.add(UserHandle._(usersPtr.elementAt(i).value)); - } - - return result; - } - - Future removeUser(App app, User user) { - final completer = Completer(); - _realmLib.invokeGetBool( - () => _realmLib.realm_app_remove_user( - app.handle._pointer, - user.handle._pointer, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Remove user failed"); - return completer.future; - } - - void switchUser(App application, User user) { - return using((arena) { - _realmLib.invokeGetBool( - () => _realmLib.realm_app_switch_user( - application.handle._pointer, - user.handle._pointer, - ), - "Switch user failed"); - }); - } - - void reconnect(App application) { - _realmLib.realm_app_sync_client_reconnect( - application.handle._pointer, - ); - } - - String getBaseUrl(App app) { - final customDataPtr = _realmLib.realm_app_get_base_url(app.handle._pointer); - return customDataPtr.cast().toRealmDartString(freeRealmMemory: true)!; - } - - Future updateBaseUrl(App app, Uri? baseUrl) { - final completer = Completer(); - using((arena) { - _realmLib.invokeGetBool( - () => _realmLib.realm_app_update_base_url( - app.handle._pointer, - baseUrl?.toString().toCharPtr(arena) ?? nullptr, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Update base URL failed"); - }); - return completer.future; - } - - String? userGetCustomData(User user) { - final customDataPtr = _realmLib.realm_user_get_custom_data(user.handle._pointer); - return customDataPtr.cast().toRealmDartString(freeRealmMemory: true, treatEmptyAsNull: true); - } - - Future userRefreshCustomData(App app, User user) { - final completer = Completer(); - _realmLib.invokeGetBool( - () => _realmLib.realm_app_refresh_custom_data( - app.handle._pointer, - user.handle._pointer, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Refresh custom data failed"); - return completer.future; - } - - Future userLinkCredentials(App app, User user, Credentials credentials) { - final completer = Completer(); - _realmLib.invokeGetBool( - () => _realmLib.realm_app_link_user( - app.handle._pointer, - user.handle._pointer, - credentials.handle._pointer, - _realmLib.addresses.realm_dart_user_completion_callback, - _createAsyncUserCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Link credentials failed"); - return completer.future; - } - - UserState userGetState(User user) { - final nativeUserState = _realmLib.realm_user_get_state(user.handle._pointer); - return UserState.values.fromIndex(nativeUserState); - } - - String userGetId(User user) { - final idPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_identity(user.handle._pointer), "Error while getting user id"); - final userId = idPtr.cast().toDartString(); - return userId; - } - - AppHandle userGetApp(UserHandle userHandle) { - final appPtr = _realmLib.realm_user_get_app(userHandle._pointer); - if (appPtr == nullptr) { - throw RealmException('User does not have an associated app. This is likely due to the user being logged out.'); - } - - return AppHandle._(appPtr); - } - - List userGetIdentities(User user) { - return using((arena) { - return _userGetIdentities(user, arena); - }); - } - - List _userGetIdentities(User user, Arena arena, {int expectedSize = 2}) { - final actualCount = arena(); - final identitiesPtr = arena(expectedSize); - _realmLib.invokeGetBool(() => _realmLib.realm_user_get_all_identities(user.handle._pointer, identitiesPtr, expectedSize, actualCount)); - - if (expectedSize < actualCount.value) { - // The supplied array was too small - resize it - arena.free(identitiesPtr); - return _userGetIdentities(user, arena, expectedSize: actualCount.value); - } - - final result = []; - for (var i = 0; i < actualCount.value; i++) { - final identity = identitiesPtr.elementAt(i).ref; - - result.add(UserIdentityInternal.create( - identity.id.cast().toRealmDartString(freeRealmMemory: true)!, AuthProviderTypeInternal.getByValue(identity.provider_type))); - } - - return result; - } - - Future userLogOut(User user) { - _realmLib.invokeGetBool(() => _realmLib.realm_user_log_out(user.handle._pointer), "Logout failed"); - return Future.value(); - } - - String? userGetDeviceId(User user) { - final deviceId = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_device_id(user.handle._pointer)); - return deviceId.cast().toRealmDartString(treatEmptyAsNull: true, freeRealmMemory: true); - } - - AuthProviderType userGetCredentialsProviderType(Credentials credentials) { - final provider = _realmLib.realm_auth_credentials_get_provider(credentials.handle._pointer); - return AuthProviderTypeInternal.getByValue(provider); - } - - UserProfile userGetProfileData(User user) { - final data = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_profile_data(user.handle._pointer)); - final dynamic profileData = jsonDecode(data.cast().toRealmDartString(freeRealmMemory: true)!); - return UserProfile(profileData as Map); - } - - String userGetRefreshToken(User user) { - final token = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_refresh_token(user.handle._pointer)); - return token.cast().toRealmDartString(freeRealmMemory: true)!; - } - - String userGetAccessToken(User user) { - final token = _realmLib.invokeGetPointer(() => _realmLib.realm_user_get_access_token(user.handle._pointer)); - return token.cast().toRealmDartString(freeRealmMemory: true)!; - } - - SessionHandle realmGetSession(Realm realm) { - return SessionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_session_get(realm.handle._pointer)), realm.handle); - } - - String sessionGetPath(Session session) { - return _realmLib.realm_sync_session_get_file_path(session.handle._pointer).cast().toRealmDartString()!; - } - - SessionState sessionGetState(Session session) { - final value = _realmLib.realm_sync_session_get_state(session.handle._pointer); - return _convertCoreSessionState(value); - } - - ConnectionState sessionGetConnectionState(Session session) { - final value = _realmLib.realm_sync_session_get_connection_state(session.handle._pointer); - return ConnectionState.values[value]; - } - - UserHandle sessionGetUser(Session session) { - return UserHandle._(_realmLib.realm_sync_session_get_user(session.handle._pointer)); - } - - SessionState _convertCoreSessionState(int value) { - switch (value) { - case 0: // RLM_SYNC_SESSION_STATE_ACTIVE - case 1: // RLM_SYNC_SESSION_STATE_DYING - return SessionState.active; - case 2: // RLM_SYNC_SESSION_STATE_INACTIVE - case 3: // RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN - case 4: // RLM_SYNC_SESSION_STATE_PAUSED - return SessionState.inactive; - default: - throw Exception("Unexpected SessionState: $value"); - } - } - - void sessionPause(Session session) { - _realmLib.realm_sync_session_pause(session.handle._pointer); - } - - void sessionResume(Session session) { - _realmLib.realm_sync_session_resume(session.handle._pointer); - } - - RealmSyncSessionConnectionStateNotificationTokenHandle sessionRegisterProgressNotifier( - Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { - final isStreaming = mode == ProgressMode.reportIndefinitely; - final callback = Pointer.fromFunction(_syncProgressCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle._pointer); - final tokenPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_sync_session_register_progress_notifier( - session.handle._pointer, - _realmLib.addresses.realm_dart_sync_progress_callback, - direction.index, - isStreaming, - userdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free)); - return RealmSyncSessionConnectionStateNotificationTokenHandle._(tokenPtr); - } - - static void _syncProgressCallback(Object userdata, int transferred, int transferable, double estimate) { - final controller = userdata as ProgressNotificationsController; - - controller.onProgress(transferred, transferable); - } - - RealmSyncSessionConnectionStateNotificationTokenHandle sessionRegisterConnectionStateNotifier(Session session, SessionConnectionStateController controller) { - final callback = Pointer.fromFunction(_onConnectionStateChange); - final userdata = _realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle._pointer); - final notification_token = _realmLib.realm_sync_session_register_connection_state_change_callback( - session.handle._pointer, - _realmLib.addresses.realm_dart_sync_connection_state_changed_callback, - userdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free, - ); - return RealmSyncSessionConnectionStateNotificationTokenHandle._(notification_token); - } - - static void _onConnectionStateChange(Object userdata, int oldState, int newState) { - final controller = userdata as SessionConnectionStateController; - - controller.onConnectionStateChange(ConnectionState.values[oldState], ConnectionState.values[newState]); - } - - Future sessionWaitForUpload(Session session, [CancellationToken? cancellationToken]) { - final completer = CancellableCompleter(cancellationToken); - if (!completer.isCancelled) { - final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - _realmLib.realm_sync_session_wait_for_upload_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, - userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - } - return completer.future; - } - - Future sessionWaitForDownload(Session session, [CancellationToken? cancellationToken]) { - final completer = CancellableCompleter(cancellationToken); - if (!completer.isCancelled) { - final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, - userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - } - return completer.future; - } - - static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { - final completer = userdata as CancellableCompleter; - if (completer.isCancelled) { - return; - } - if (errorCode != nullptr) { - // Throw RealmException instead of RealmError to be recoverable by the user. - completer.completeError(RealmException(errorCode.toSyncError().toString())); - } else { - completer.complete(); - } - } - - String getBundleId() { - readBundleId() { - try { - if (!isFlutterPlatform || Platform.environment.containsKey('FLUTTER_TEST')) { - var pubspecPath = path.join(path.current, 'pubspec.yaml'); - var pubspecFile = File(pubspecPath); - - if (pubspecFile.existsSync()) { - final pubspec = Pubspec.parse(pubspecFile.readAsStringSync()); - return pubspec.name; - } - } - - if (Platform.isAndroid) { - return _realmLib.realm_dart_get_bundle_id().cast().toDartString(); - } - - final getBundleIdFunc = _pluginLib.lookupFunction Function(), Pointer Function()>("realm_dart_get_bundle_id"); - final bundleIdPtr = getBundleIdFunc(); - return bundleIdPtr.cast().toDartString(); - } on Exception catch (_) { - //Never fail on bundleId. Use fallback value. - } - - //Fallback value - return "realm_bundle_id"; - } - - String bundleId = readBundleId(); - const salt = [82, 101, 97, 108, 109, 32, 105, 115, 32, 103, 114, 101, 97, 116]; - return base64Encode(sha256.convert([...salt, ...utf8.encode(bundleId)]).bytes); - } - - String _getAppDirectoryFromPlugin() { - assert(isFlutterPlatform); - - final getAppDirFunc = _pluginLib.lookupFunction Function(), Pointer Function()>("realm_dart_get_app_directory"); - final dirNamePtr = getAppDirFunc(); - final dirName = Platform.isWindows ? dirNamePtr.cast().toDartString() : dirNamePtr.cast().toDartString(); - - return dirName; - } - - String getAppDirectory() { - try { - if (!isFlutterPlatform || Platform.environment.containsKey('FLUTTER_TEST')) { - return Directory.current.absolute.path; // dart or flutter test - } - - // Flutter from here on.. - - if (Platform.isAndroid || Platform.isIOS) { - return getFilesPath(); - } - - if (Platform.isLinux) { - String appSupportDir = PlatformEx.fromEnvironment( - "XDG_DATA_HOME", - defaultValue: PlatformEx.fromEnvironment( - "HOME", - defaultValue: Directory.current.absolute.path, - ), - ); - return path.join(appSupportDir, ".local/share", _getAppDirectoryFromPlugin()); - } - - if (Platform.isMacOS) { - return _getAppDirectoryFromPlugin(); - } - - if (Platform.isWindows) { - return _getAppDirectoryFromPlugin(); - } - - throw UnsupportedError("Platform ${Platform.operatingSystem} is not supported"); - } catch (e) { - throw RealmException('Cannot get app directory. Error: $e'); - } - } - - Future deleteUser(App app, User user) { - final completer = Completer(); - _realmLib.invokeGetBool( - () => _realmLib.realm_app_delete_user( - app.handle._pointer, - user.handle._pointer, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - ), - "Delete user failed"); - return completer.future; - } - - bool isFrozen(Realm realm) { - return _realmLib.realm_is_frozen(realm.handle._pointer.cast()); - } - - RealmHandle freeze(Realm realm) { - final ptr = _realmLib.invokeGetPointer(() => _realmLib.realm_freeze(realm.handle._pointer)); - return RealmHandle._(ptr); - } - - RealmResultsHandle resolveResults(RealmResults realmResults, Realm frozenRealm) { - final ptr = _realmLib.invokeGetPointer(() => _realmLib.realm_results_resolve_in(realmResults.handle._pointer, frozenRealm.handle._pointer)); - return RealmResultsHandle._(ptr, frozenRealm.handle); - } - - RealmObjectHandle? resolveObject(RealmObjectBase object, Realm frozenRealm) { - return using((Arena arena) { - final resultPtr = arena>(); - _realmLib.invokeGetBool(() => _realmLib.realm_object_resolve_in(object.handle._pointer, frozenRealm.handle._pointer, resultPtr)); - return resultPtr == nullptr ? null : RealmObjectHandle._(resultPtr.value, frozenRealm.handle); - }); - } - - RealmListHandle? resolveList(ManagedRealmList list, Realm frozenRealm) { - return using((Arena arena) { - final resultPtr = arena>(); - _realmLib.invokeGetBool(() => _realmLib.realm_list_resolve_in(list.handle._pointer, frozenRealm.handle._pointer, resultPtr)); - return resultPtr == nullptr ? null : RealmListHandle._(resultPtr.value, frozenRealm.handle); - }); - } - - RealmSetHandle? resolveSet(ManagedRealmSet set, Realm frozenRealm) { - return using((Arena arena) { - final resultPtr = arena>(); - _realmLib.invokeGetBool(() => _realmLib.realm_set_resolve_in(set.handle._pointer, frozenRealm.handle._pointer, resultPtr)); - return resultPtr == nullptr ? null : RealmSetHandle._(resultPtr.value, frozenRealm.handle); - }); - } - - RealmMapHandle? resolveMap(ManagedRealmMap map, Realm frozenRealm) { - return using((Arena arena) { - final resultPtr = arena>(); - _realmLib.invokeGetBool(() => _realmLib.realm_dictionary_resolve_in(map.handle._pointer, frozenRealm.handle._pointer, resultPtr)); - return resultPtr == nullptr ? null : RealmMapHandle._(resultPtr.value, frozenRealm.handle); - }); - } - - static void _app_api_key_completion_callback(Pointer userdata, Pointer apiKey, Pointer error) { - final Completer completer = userdata.toObject(); - if (error != nullptr) { - completer.completeWithAppError(error); - return; - } - completer.complete(apiKey.ref.toDart()); - } - - static void _app_api_key_array_completion_callback(Pointer userdata, Pointer apiKey, int size, Pointer error) { - final Completer> completer = userdata.toObject(); - - if (error != nullptr) { - completer.completeWithAppError(error); - return; - } - - final result = []; - for (var i = 0; i < size; i++) { - result.add(apiKey[i].toDart()); - } - - completer.complete(result); - } - - Future createApiKey(User user, String name) { - return using((Arena arena) { - final namePtr = name.toCharPtr(arena); - final completer = Completer(); - _realmLib.invokeGetBool(() => _realmLib.realm_app_user_apikey_provider_client_create_apikey( - user.app.handle._pointer, - user.handle._pointer, - namePtr, - _realmLib.addresses.realm_dart_apikey_callback, - _createAsyncApikeyCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - - return completer.future; - }); - } - - Future fetchApiKey(User user, ObjectId id) { - return using((Arena arena) { - final completer = Completer(); - final native_id = id.toNative(arena); - _realmLib.invokeGetBool(() => _realmLib.realm_app_user_apikey_provider_client_fetch_apikey( - user.app.handle._pointer, - user.handle._pointer, - native_id.ref, - _realmLib.addresses.realm_dart_apikey_callback, - _createAsyncApikeyCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - - return completer.future; - }); - } - - Future> fetchAllApiKeys(User user) { - return using((Arena arena) { - final completer = Completer>(); - _realmLib.invokeGetBool(() => _realmLib.realm_app_user_apikey_provider_client_fetch_apikeys( - user.app.handle._pointer, - user.handle._pointer, - _realmLib.addresses.realm_dart_apikey_list_callback, - _createAsyncApikeyListCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - - return completer.future; - }); - } - - Future deleteApiKey(User user, ObjectId id) { - return using((Arena arena) { - final completer = Completer(); - final native_id = id.toNative(arena); - _realmLib.invokeGetBool(() => _realmLib.realm_app_user_apikey_provider_client_delete_apikey( - user.app.handle._pointer, - user.handle._pointer, - native_id.ref, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - - return completer.future; - }); - } - - Pointer _createAsyncCallbackUserdata(Completer completer) { - final callback = Pointer.fromFunction< - Void Function( - Pointer, - Pointer, - )>(_void_completion_callback); - - final userdata = _realmLib.realm_dart_userdata_async_new( - completer, - callback.cast(), - scheduler.handle._pointer, - ); - - return userdata.cast(); - } - - Pointer _createAsyncApikeyCallbackUserdata(Completer completer) { - final callback = Pointer.fromFunction< - Void Function( - Pointer, - Pointer, - Pointer, - )>(_app_api_key_completion_callback); - - final userdata = _realmLib.realm_dart_userdata_async_new( - completer, - callback.cast(), - scheduler.handle._pointer, - ); - - return userdata.cast(); - } - - Pointer _createAsyncApikeyListCallbackUserdata(Completer> completer) { - final callback = Pointer.fromFunction< - Void Function( - Pointer, - Pointer, - Size count, - Pointer, - )>(_app_api_key_array_completion_callback); - - final userdata = _realmLib.realm_dart_userdata_async_new( - completer, - callback.cast(), - scheduler.handle._pointer, - ); - - return userdata.cast(); - } - - Future disableApiKey(User user, ObjectId objectId) { - return using((Arena arena) { - final completer = Completer(); - final native_id = objectId.toNative(arena); - - _realmLib.invokeGetBool(() => _realmLib.realm_app_user_apikey_provider_client_disable_apikey( - user.app.handle._pointer, - user.handle._pointer, - native_id.ref, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - - return completer.future; - }); - } - - Future enableApiKey(User user, ObjectId objectId) { - return using((Arena arena) { - final completer = Completer(); - final native_id = objectId.toNative(arena); - _realmLib.invokeGetBool(() => _realmLib.realm_app_user_apikey_provider_client_enable_apikey( - user.app.handle._pointer, - user.handle._pointer, - native_id.ref, - _realmLib.addresses.realm_dart_void_completion_callback, - _createAsyncCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - - return completer.future; - }); - } - - static void _call_app_function_callback(Pointer userdata, Pointer response, Pointer error) { - final Completer completer = userdata.toObject(); - - if (error != nullptr) { - completer.completeWithAppError(error); - return; - } - - final stringResponse = response.cast().toRealmDartString()!; - completer.complete(stringResponse); - } - - Pointer _createAsyncFunctionCallbackUserdata(Completer completer) { - final callback = Pointer.fromFunction< - Void Function( - Pointer, - Pointer, - Pointer, - )>(_call_app_function_callback); - - final userdata = _realmLib.realm_dart_userdata_async_new( - completer, - callback.cast(), - scheduler.handle._pointer, - ); - - return userdata.cast(); - } - - Future callAppFunction(App app, User user, String functionName, String? argsAsJSON) { - return using((arena) { - final completer = Completer(); - _realmLib.invokeGetBool(() => _realmLib.realm_app_call_function( - app.handle._pointer, - user.handle._pointer, - functionName.toCharPtr(arena), - argsAsJSON?.toCharPtr(arena) ?? nullptr, - nullptr, - _realmLib.addresses.realm_dart_return_string_callback, - _createAsyncFunctionCallbackUserdata(completer), - _realmLib.addresses.realm_dart_userdata_async_free, - )); - return completer.future; - }); - } - - bool compact(Realm realm) { - return using((arena) { - final out_did_compact = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_compact(realm.handle._pointer, out_did_compact)); - return out_did_compact.value; - }); - } - - bool immediatelyRunFileActions(App app, String realmPath) { - return using((arena) { - final out_did_run = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_immediately_run_file_actions(app.handle._pointer, realmPath.toCharPtr(arena), out_did_run), - "An error occurred while resetting the Realm. Check if the file is in use: '$realmPath'"); - return out_did_run.value; - }); - } - - void writeCopy(Realm realm, Configuration config) { - final configHandle = _createConfig(config); - _realmLib.invokeGetBool(() => _realmLib.realm_convert_with_config(realm.handle._pointer, configHandle._pointer, false)); - } - - void _createCollection(Realm realm, RealmValue value, Pointer Function() createList, Pointer Function() createMap) { - CollectionHandleBase? collectionHandle; - try { - switch (value.collectionType) { - case RealmCollectionType.list: - final listPointer = _realmLib.invokeGetPointer(createList); - final listHandle = RealmListHandle._(listPointer, realm.handle); - collectionHandle = listHandle; - - final list = realm.createList(listHandle, null); - - // Necessary since Core will not clear the collection if the value was already a collection - list.clear(); - - for (final item in value.value as List) { - list.add(item); - } - case RealmCollectionType.map: - final mapPointer = _realmLib.invokeGetPointer(createMap); - final mapHandle = RealmMapHandle._(mapPointer, realm.handle); - collectionHandle = mapHandle; - - final map = realm.createMap(mapHandle, null); - - // Necessary since Core will not clear the collection if the value was already a collection - map.clear(); - - for (final kvp in (value.value as Map).entries) { - map[kvp.key] = kvp.value; - } - default: - throw RealmStateError('_createCollection invoked with type that is not list or map.'); - } - } finally { - collectionHandle?.release(); - } - } - - void setLogLevel(LogLevel level, {required LogCategory category}) { - using((arena) { - _realmLib.realm_set_log_level_category(category.toString().toCharPtr(arena), level.index); - }); - } - - List getAllCategoryNames() { - return using((arena) { - final count = _realmLib.realm_get_category_names(0, nullptr); - final out_values = arena>(count); - _realmLib.realm_get_category_names(count, out_values); - return [for (int i = 0; i < count; i++) out_values[i].cast().toDartString()]; - }); - } -} - -class LastError { - final int code; - final String? message; - final Object? userError; - - LastError(this.code, [this.message, this.userError]); - - @override - String toString() => "${message ?? 'No message'}. Error code: $code."; -} - -// Flag to enable trace on finalization. -// -// Be aware that the trace is likely late, and it might in rare case be missing, -// as there are no absolute guarantees with Finalizer. -// -// It is often beneficial to also instrument the native realm_release to -// print the address released to get the exact time. -const _enableFinalizerTrace = false; - -// Level used for finalization trace, if enabled. -const _finalizerTraceLevel = LogLevel.trace; - -void _traceFinalization(Object o) { - Realm.logger.log(_finalizerTraceLevel, 'Finalizing: $o'); -} - -final _debugFinalizer = Finalizer(_traceFinalization); - -void _setupFinalizationTrace(Object value, Object finalizationToken) { - _debugFinalizer.attach(value, finalizationToken, detach: value); -} - -void _tearDownFinalizationTrace(Object value, Object finalizationToken) { - _debugFinalizer.detach(value); - _traceFinalization(finalizationToken); -} - -abstract class HandleBase implements Finalizable { - late Pointer _finalizableHandle; - Pointer _pointer; - bool get released => _pointer == nullptr; - final bool isUnowned; - - @pragma('vm:never-inline') - void keepAlive() {} - - HandleBase(this._pointer, int size) : isUnowned = false { - _finalizableHandle = _realmLib.realm_attach_finalizer(this, _pointer.cast(), size); - - if (_enableFinalizerTrace) { - _setupFinalizationTrace(this, _pointer); - } - } - - HandleBase.unowned(this._pointer) : isUnowned = true; - - @override - String toString() => "${_pointer.toString()} value=${_pointer.cast().value}${isUnowned ? ' (unowned)' : ''}"; - - /// @nodoc - /// A method that will be invoked just before the handle is released. Allows to cleanup - /// any custom data that inheritors are storing. - void _releaseCore() {} - - void release() { - if (released) { - return; - } - - _releaseCore(); - - if (!isUnowned) { - _realmLib.realm_detach_finalizer(_finalizableHandle, this); - - _realmLib.realm_release(_pointer.cast()); - } - - _pointer = nullptr; - - if (_enableFinalizerTrace) { - _tearDownFinalizationTrace(this, _pointer); - } - } -} - -class FinalizationToken { - final WeakReference root; - final int id; - - FinalizationToken(RealmHandle handle, this.id) : root = WeakReference(handle); -} - -// This finalizer is intended to prevent the list of children in the RealmHandle -// from growing endlessly. It's not intended to replace the native finalizer which -// will free the actual resources owned by the handle. -final _rootedHandleFinalizer = Finalizer((token) { - token.root.target?.removeChild(token.id); -}); - -abstract class RootedHandleBase extends HandleBase { - final RealmHandle _root; - int? _id; - - bool get shouldRoot => _root.isUnowned; - - RootedHandleBase(this._root, Pointer pointer, int size) : super(pointer, size) { - if (shouldRoot) { - _id = _root.addChild(this); - } - } - - @override - void _releaseCore() { - if (_id != null) { - _root.removeChild(_id!); - } - } -} - -abstract class CollectionHandleBase extends RootedHandleBase { - CollectionHandleBase(super.root, super.pointer, super.size); -} - -class SchemaHandle extends HandleBase { - SchemaHandle._(Pointer pointer) : super(pointer, 24); - - SchemaHandle.unowned(super.pointer) : super.unowned(); -} - -class ConfigHandle extends HandleBase { - ConfigHandle._(Pointer pointer) : super(pointer, 512); -} - -class RealmHandle extends HandleBase { - int _counter = 0; - - final Map> _children = {}; - - RealmHandle._(Pointer pointer) : super(pointer, 24); - - RealmHandle._unowned(super.pointer) : super.unowned(); - - int addChild(RootedHandleBase child) { - final id = _counter++; - _children[id] = WeakReference(child); - _rootedHandleFinalizer.attach(this, FinalizationToken(this, id), detach: this); - return id; - } - - void removeChild(int id) { - final child = _children.remove(id); - if (child != null) { - final target = child.target; - if (target != null) { - _rootedHandleFinalizer.detach(target); - } - } - } - - @override - void _releaseCore() { - final keys = _children.keys.toList(); - - for (final key in keys) { - _children[key]?.target?.release(); - } - } -} - -class SchedulerHandle extends HandleBase { - SchedulerHandle._(Pointer pointer) : super(pointer, 24); -} - -class RealmObjectHandle extends RootedHandleBase { - RealmObjectHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 112); -} - -class _RealmLinkHandle { - final int targetKey; - final int classKey; - _RealmLinkHandle._(realm_link_t link) - : targetKey = link.target, - classKey = link.target_table; -} - -class RealmResultsHandle extends RootedHandleBase { - RealmResultsHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 872); -} - -class RealmListHandle extends CollectionHandleBase { - RealmListHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 88); -} - -class RealmSetHandle extends RootedHandleBase { - RealmSetHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 96); -} - -class RealmMapHandle extends CollectionHandleBase { - RealmMapHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 96); // TODO: check size -} - -class _RealmQueryHandle extends RootedHandleBase { - _RealmQueryHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 256); -} - -class RealmCallbackTokenHandle extends RootedHandleBase { - RealmCallbackTokenHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 32); -} - -class RealmNotificationTokenHandle extends RootedHandleBase { - RealmNotificationTokenHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 32); -} - -class UserNotificationTokenHandle extends HandleBase { - UserNotificationTokenHandle._(Pointer pointer) : super(pointer, 32); -} - -class RealmSyncSessionConnectionStateNotificationTokenHandle extends HandleBase { - RealmSyncSessionConnectionStateNotificationTokenHandle._(Pointer pointer) : super(pointer, 32); -} - -class RealmCollectionChangesHandle extends HandleBase { - RealmCollectionChangesHandle._(Pointer pointer) : super(pointer, 256); -} - -class RealmMapChangesHandle extends HandleBase { - RealmMapChangesHandle._(Pointer pointer) : super(pointer, 256); -} - -class RealmObjectChangesHandle extends HandleBase { - RealmObjectChangesHandle._(Pointer pointer) : super(pointer, 256); -} - -class RealmAppCredentialsHandle extends HandleBase { - RealmAppCredentialsHandle._(Pointer pointer) : super(pointer, 16); -} - -class RealmHttpTransportHandle extends HandleBase { - RealmHttpTransportHandle._(Pointer pointer) : super(pointer, 24); -} - -class AppConfigHandle extends HandleBase { - AppConfigHandle._(Pointer pointer) : super(pointer, 8); -} - -class SyncClientConfigHandle extends HandleBase { - SyncClientConfigHandle._(Pointer pointer) : super(pointer, 8); -} - -class AppHandle extends HandleBase { - AppHandle._(Pointer pointer) : super(pointer, 16); -} - -class UserHandle extends HandleBase { - UserHandle._(Pointer pointer) : super(pointer, 24); -} - -class SubscriptionHandle extends HandleBase { - SubscriptionHandle._(Pointer pointer) : super(pointer, 184); -} - -class RealmAsyncOpenTaskHandle extends HandleBase { - RealmAsyncOpenTaskHandle._(Pointer pointer) : super(pointer, 32); -} - -class RealmAsyncOpenTaskProgressNotificationTokenHandle extends HandleBase { - RealmAsyncOpenTaskProgressNotificationTokenHandle._(Pointer pointer) : super(pointer, 40); -} - -class SubscriptionSetHandle extends RootedHandleBase { - @override - bool get shouldRoot => true; - - SubscriptionSetHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 128); -} - -class MutableSubscriptionSetHandle extends SubscriptionSetHandle { - MutableSubscriptionSetHandle._(Pointer pointer, RealmHandle root) : super._(pointer.cast(), root); - - Pointer get _mutablePointer => super._pointer.cast(); -} - -class SessionHandle extends RootedHandleBase { - @override - bool get shouldRoot => true; - - SessionHandle._(Pointer pointer, RealmHandle root) : super(root, pointer, 24); -} - -extension on List { - Pointer toCharPtr(Allocator allocator) { - return toUint8Ptr(allocator).cast(); - } - - Pointer toUint8Ptr(Allocator allocator) { - final nativeSize = length + 1; - final result = allocator(nativeSize); - final Uint8List native = result.asTypedList(nativeSize); - native.setAll(0, this); // copy - native.last = 0; // zero terminate - return result.cast(); - } -} - -extension _StringEx on String { - Pointer toCharPtr(Allocator allocator) { - final units = utf8.encode(this); - return units.toCharPtr(allocator).cast(); - } - - Pointer toRealmString(Allocator allocator) { - final realm_string = allocator(); - final units = utf8.encode(this); - realm_string.ref.data = units.toCharPtr(allocator).cast(); - realm_string.ref.size = units.length; - return realm_string; - } -} - -extension _RealmLibraryEx on RealmLibrary { - void invokeGetBool(bool Function() callback, [String? errorMessage]) { - bool success = callback(); - if (!success) { - realmCore.throwLastError(errorMessage); - } - } - - Pointer invokeGetPointer(Pointer Function() callback, [String? errorMessage]) { - final result = callback(); - if (result == nullptr) { - realmCore.throwLastError(errorMessage); - } - return result; - } -} - -Pointer _toRealmValue(Object? value, Allocator allocator) { - final realm_value = allocator(); - if (value is RealmValue && value.type.isCollection) { - throw RealmError( - "Don't use _toPrimitiveValue if the value may contain collections. Use storeValue instead. This is a bug in the Realm Flutter SDK and should be reported to https://github.com/realm/realm-dart/issues/new"); - } - _intoRealmValue(value, realm_value.ref, allocator); - return realm_value; -} - -const int _microsecondsPerSecond = 1000 * 1000; -const int _nanosecondsPerMicrosecond = 1000; - -void _intoRealmQueryArg(Object? value, Pointer realm_query_arg, Allocator allocator) { - if (value is Iterable) { - realm_query_arg.ref.nb_args = value.length; - realm_query_arg.ref.is_list = true; - realm_query_arg.ref.arg = allocator(value.length); - int i = 0; - for (var item in value) { - _intoRealmValue(item, realm_query_arg.ref.arg[i], allocator); - i++; - } - } else { - realm_query_arg.ref.arg = allocator(); - realm_query_arg.ref.nb_args = 1; - realm_query_arg.ref.is_list = false; - _intoRealmValueHack(value, realm_query_arg.ref.arg.ref, allocator); - } -} - -void _intoRealmValueHack(Object? value, realm_value realm_value, Allocator allocator) { - if (value is GeoShape) { - _intoRealmValue(value.toString(), realm_value, allocator); - } else if (value is RealmValueType) { - _intoRealmValue(value.toQueryArgString(), realm_value, allocator); - } else { - _intoRealmValue(value, realm_value, allocator); - } -} - -void _intoRealmValue(Object? value, realm_value realm_value, Allocator allocator) { - if (value == null) { - realm_value.type = realm_value_type.RLM_TYPE_NULL; - } else if (value is RealmObjectBase) { - // when converting a RealmObjectBase to realm_value.link we assume the object is managed - final link = realmCore._getObjectAsLink(value); - realm_value.values.link.target = link.targetKey; - realm_value.values.link.target_table = link.classKey; - realm_value.type = realm_value_type.RLM_TYPE_LINK; - } else if (value is int) { - realm_value.values.integer = value; - realm_value.type = realm_value_type.RLM_TYPE_INT; - } else if (value is bool) { - realm_value.values.boolean = value; - realm_value.type = realm_value_type.RLM_TYPE_BOOL; - } else if (value is String) { - String string = value; - final units = utf8.encode(string); - final result = allocator(units.length); - final Uint8List nativeString = result.asTypedList(units.length); - nativeString.setAll(0, units); - realm_value.values.string.data = result.cast(); - realm_value.values.string.size = units.length; - realm_value.type = realm_value_type.RLM_TYPE_STRING; - } else if (value is double) { - realm_value.values.dnum = value; - realm_value.type = realm_value_type.RLM_TYPE_DOUBLE; - } else if (value is ObjectId) { - final bytes = value.bytes; - for (var i = 0; i < 12; i++) { - realm_value.values.object_id.bytes[i] = bytes[i]; - } - realm_value.type = realm_value_type.RLM_TYPE_OBJECT_ID; - } else if (value is Uuid) { - final bytes = value.bytes.asUint8List(); - for (var i = 0; i < 16; i++) { - realm_value.values.uuid.bytes[i] = bytes[i]; - } - realm_value.type = realm_value_type.RLM_TYPE_UUID; - } else if (value is DateTime) { - final microseconds = value.toUtc().microsecondsSinceEpoch; - final seconds = microseconds ~/ _microsecondsPerSecond; - int nanoseconds = _nanosecondsPerMicrosecond * (microseconds % _microsecondsPerSecond); - if (microseconds < 0 && nanoseconds != 0) { - nanoseconds = nanoseconds - _nanosecondsPerMicrosecond * _microsecondsPerSecond; - } - realm_value.values.timestamp.seconds = seconds; - realm_value.values.timestamp.nanoseconds = nanoseconds; - realm_value.type = realm_value_type.RLM_TYPE_TIMESTAMP; - } else if (value is Decimal128) { - realm_value.values.decimal128 = value.value; - realm_value.type = realm_value_type.RLM_TYPE_DECIMAL128; - } else if (value is Uint8List) { - realm_value.type = realm_value_type.RLM_TYPE_BINARY; - realm_value.values.binary.size = value.length; - realm_value.values.binary.data = allocator(value.length); - realm_value.values.binary.data.asTypedList(value.length).setAll(0, value); - } else if (value is RealmValue) { - if (value.type == List) { - realm_value.type = realm_value_type.RLM_TYPE_LIST; - } else if (value.type == Map) { - realm_value.type = realm_value_type.RLM_TYPE_DICTIONARY; - } else { - return _intoRealmValue(value.value, realm_value, allocator); - } - } else { - throw RealmException("Property type ${value.runtimeType} not supported"); - } -} - -extension on Pointer { - Object? toDartValue(Realm realm, Pointer Function()? getList, Pointer Function()? getMap) { - if (this == nullptr) { - throw RealmException("Can not convert nullptr realm_value to Dart value"); - } - return ref.toDartValue(realm: realm, getList: getList, getMap: getMap); - } -} - -extension on realm_value_t { - Object? toPrimitiveValue() => toDartValue(realm: null, getList: null, getMap: null); - - Object? toDartValue({required Realm? realm, required Pointer Function()? getList, required Pointer Function()? getMap}) { - switch (type) { - case realm_value_type.RLM_TYPE_NULL: - return null; - case realm_value_type.RLM_TYPE_INT: - return values.integer; - case realm_value_type.RLM_TYPE_BOOL: - return values.boolean; - case realm_value_type.RLM_TYPE_STRING: - return values.string.data.cast().toRealmDartString(length: values.string.size)!; - case realm_value_type.RLM_TYPE_FLOAT: - return values.fnum; - case realm_value_type.RLM_TYPE_DOUBLE: - return values.dnum; - case realm_value_type.RLM_TYPE_LINK: - if (realm == null) { - throw RealmError("A realm instance is required to resolve Backlinks"); - } - final objectKey = values.link.target; - final classKey = values.link.target_table; - if (realm.metadata.getByClassKeyIfExists(classKey) == null) return null; // temprorary workaround to avoid crash on assertion - return realmCore._getObject(realm, classKey, objectKey); - case realm_value_type.RLM_TYPE_BINARY: - return Uint8List.fromList(values.binary.data.asTypedList(values.binary.size)); - case realm_value_type.RLM_TYPE_TIMESTAMP: - final seconds = values.timestamp.seconds; - final nanoseconds = values.timestamp.nanoseconds; - return DateTime.fromMicrosecondsSinceEpoch(seconds * _microsecondsPerSecond + nanoseconds ~/ _nanosecondsPerMicrosecond, isUtc: true); - case realm_value_type.RLM_TYPE_DECIMAL128: - var decimal = values.decimal128; // NOTE: Does not copy the struct! - decimal = _realmLib.realm_dart_decimal128_copy(decimal); // This is a workaround to that - return Decimal128Internal.fromNative(decimal); - case realm_value_type.RLM_TYPE_OBJECT_ID: - return ObjectId.fromBytes(values.object_id.bytes.toList(12)); - case realm_value_type.RLM_TYPE_UUID: - final listInt = values.uuid.bytes.toList(16); - return Uuid.fromBytes(Uint8List.fromList(listInt).buffer); - case realm_value_type.RLM_TYPE_LIST: - if (getList == null || realm == null) { - throw RealmException('toDartValue called with a list argument but without a list getter'); - } - - final listPointer = _realmLib.invokeGetPointer(() => getList()); - final listHandle = RealmListHandle._(listPointer, realm.handle); - return realm.createList(listHandle, null); - case realm_value_type.RLM_TYPE_DICTIONARY: - if (getMap == null || realm == null) { - throw RealmException('toDartValue called with a list argument but without a list getter'); - } - - final mapPointer = _realmLib.invokeGetPointer(() => getMap()); - final mapHandle = RealmMapHandle._(mapPointer, realm.handle); - return realm.createMap(mapHandle, null); - default: - throw RealmException("realm_value_type $type not supported"); - } - } -} - -extension on Array { - List toList(int count) { - final result = []; - for (var i = 0; i < count; i++) { - result.add(this[i]); - } - return result; - } -} - -extension on Pointer { - List toIntList(int count) { - List result = List.filled(count, elementAt(0).value); - for (var i = 1; i < count; i++) { - result[i] = elementAt(i).value; - } - return result; - } -} - -extension on Pointer { - List toStringList(int count) { - final result = List.filled(count, ''); - for (var i = 0; i < count; i++) { - final str_value = elementAt(i).ref.values.string; - result[i] = str_value.data.cast().toRealmDartString(length: str_value.size)!; - } - - return result; - } -} - -extension on Pointer { - T toObject() { - assert(this != nullptr, "Pointer is null"); - - Object object = _realmLib.realm_dart_persistent_handle_to_object(this); - - assert(object is T, "$T expected"); - return object as T; - } - - Object? toUserCodeError() { - if (this != nullptr) { - final result = toObject(); - _realmLib.realm_dart_delete_persistent_handle(this); - return result; - } - - return null; - } -} - -extension on Pointer { - String? toRealmDartString({bool treatEmptyAsNull = false, int? length, bool freeRealmMemory = false}) { - if (this == nullptr) { - return null; - } - - try { - final result = toDartString(length: length); - - if (treatEmptyAsNull && result == '') { - return null; - } - return result; - } finally { - if (freeRealmMemory) { - _realmLib.realm_free(cast()); - } - } - } -} - -extension on realm_sync_error { - SyncErrorDetails toSyncErrorDetails() { - final message = status.message.cast().toRealmDartString()!; - final userInfoMap = user_info_map.toMap(user_info_length); - final originalFilePathKey = c_original_file_path_key.cast().toRealmDartString(); - final recoveryFilePathKey = c_recovery_file_path_key.cast().toRealmDartString(); - - return SyncErrorDetails( - message, - status.error, - user_code_error.toUserCodeError(), - isFatal: is_fatal, - isClientResetRequested: is_client_reset_requested, - originalFilePath: userInfoMap?[originalFilePathKey], - backupFilePath: userInfoMap?[recoveryFilePathKey], - compensatingWrites: compensating_writes.toList(compensating_writes_length), - ); - } -} - -extension on Pointer { - Map? toMap(int length) { - if (this == nullptr) { - return null; - } - Map userInfoMap = {}; - for (int i = 0; i < length; i++) { - final userInfoItem = this[i]; - final key = userInfoItem.key.cast().toDartString(); - final value = userInfoItem.value.cast().toDartString(); - userInfoMap[key] = value; - } - return userInfoMap; - } -} - -extension on Pointer { - List? toList(int length) { - if (this == nullptr || length == 0) { - return null; - } - List compensatingWrites = []; - for (int i = 0; i < length; i++) { - final compensatingWrite = this[i]; - final reason = compensatingWrite.reason.cast().toDartString(); - final object_name = compensatingWrite.object_name.cast().toDartString(); - final primary_key = compensatingWrite.primary_key.toPrimitiveValue(); - compensatingWrites.add(CompensatingWriteInfo(object_name, reason, RealmValue.from(primary_key))); - } - return compensatingWrites; - } -} - -extension on Pointer { - SyncError toSyncError() { - final message = ref.message.cast().toDartString(); - final details = SyncErrorDetails(message, ref.error, ref.user_code_error.toUserCodeError()); - return SyncErrorInternal.createSyncError(details); - } -} - -extension on Object { - Pointer toPersistentHandle() { - return _realmLib.realm_dart_object_to_persistent_handle(this); - } -} - -extension on List { - UserState fromIndex(int index) { - if (!UserState.values.any((value) => value.index == index)) { - throw RealmError("Unknown user state $index"); - } - - return UserState.values[index]; - } -} - -extension on realm_property_info { - SchemaProperty toSchemaProperty() { - final linkTarget = link_target == nullptr ? null : link_target.cast().toDartString(); - return SchemaProperty(name.cast().toDartString(), RealmPropertyType.values[type], - optional: flags & realm_property_flags.RLM_PROPERTY_NULLABLE == realm_property_flags.RLM_PROPERTY_NULLABLE, - primaryKey: flags & realm_property_flags.RLM_PROPERTY_PRIMARY_KEY == realm_property_flags.RLM_PROPERTY_PRIMARY_KEY, - linkTarget: linkTarget == null || linkTarget.isEmpty ? null : linkTarget, - collectionType: RealmCollectionType.values[collection_type]); - } -} - -extension on Completer { - void completeWithAppError(Pointer error) { - final message = error.ref.message.cast().toRealmDartString()!; - final linkToLogs = error.ref.link_to_server_logs.cast().toRealmDartString(); - completeError(AppInternal.createException(message, linkToLogs, error.ref.http_status_code)); - } -} - -enum _CustomErrorCode { - noError(0), - unknownHttp(998), - unknown(999), - timeout(1000); - - final int code; - const _CustomErrorCode(this.code); -} - -enum _HttpMethod { - get, - post, - patch, - put, - delete, -} - -extension on realm_timestamp_t { - DateTime toDart() { - return DateTime.fromMicrosecondsSinceEpoch(seconds * 1000000 + nanoseconds ~/ 1000, isUtc: true); - } -} - -extension on realm_string_t { - String? toDart() => data.cast().toRealmDartString(); -} - -extension on ObjectId { - Pointer toNative(Allocator allocator) { - final result = allocator(); - for (var i = 0; i < 12; i++) { - result.ref.bytes[i] = bytes[i]; - } - return result; - } -} - -extension on realm_object_id { - ObjectId toDart() { - final buffer = Uint8List(12); - for (int i = 0; i < 12; ++i) { - buffer[i] = bytes[i]; - } - return ObjectId.fromBytes(buffer); - } -} - -extension on realm_app_user_apikey { - ApiKey toDart() => UserInternal.createApiKey( - id.toDart(), - name.cast().toDartString(), - key.cast().toRealmDartString(treatEmptyAsNull: true), - !disabled, - ); -} - -extension PlatformEx on Platform { - static String fromEnvironment(String name, {String defaultValue = ""}) { - final result = Platform.environment[name]; - if (result == null) { - return defaultValue; - } - - return result; - } -} - -/// @nodoc -class SyncErrorDetails { - final String message; - final int code; - final String? path; - final bool isFatal; - final bool isClientResetRequested; - final String? originalFilePath; - final String? backupFilePath; - final List? compensatingWrites; - final Object? userError; + String _getAppDirectoryFromPlugin() { + assert(isFlutterPlatform); - SyncErrorDetails( - this.message, - this.code, - this.userError, { - this.path, - this.isFatal = false, - this.isClientResetRequested = false, - this.originalFilePath, - this.backupFilePath, - this.compensatingWrites, - }); -} + final getAppDirFunc = _pluginLib.lookupFunction Function(), Pointer Function()>("realm_dart_get_app_directory"); + final dirNamePtr = getAppDirFunc(); + final dirName = Platform.isWindows ? dirNamePtr.cast().toDartString() : dirNamePtr.cast().toDartString(); -extension on realm_error { - LastError toLastError() { - final message = this.message.cast().toRealmDartString(); - return LastError(error, message, user_code_error.toUserCodeError()); + return dirName; } -} -extension on RealmValueType { - String toQueryArgString() { - return switch (this) { - RealmValueType.nullValue => 'null', - RealmValueType.boolean => 'bool', - RealmValueType.string => 'string', - RealmValueType.int => 'int', - RealmValueType.double => 'double', - RealmValueType.object => 'link', - RealmValueType.objectId => 'objectid', - RealmValueType.dateTime => 'date', - RealmValueType.decimal => 'decimal', - RealmValueType.uuid => 'uuid', - RealmValueType.binary => 'binary', - RealmValueType.list => 'array', - RealmValueType.map => 'object', - }; + String _getFilesPath() { + return realmLib.realm_dart_get_files_path().cast().toRealmDartString()!; } } diff --git a/packages/realm_dart/lib/src/native/realm_handle.dart b/packages/realm_dart/lib/src/native/realm_handle.dart new file mode 100644 index 000000000..91a4ac925 --- /dev/null +++ b/packages/realm_dart/lib/src/native/realm_handle.dart @@ -0,0 +1,452 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; +import 'dart:ffi'; + +import 'package:cancellation_token/cancellation_token.dart'; +import 'ffi.dart'; +import 'package:realm_common/realm_common.dart'; + +import '../logging.dart'; +import '../realm_class.dart'; +import '../realm_object.dart'; +import 'config_handle.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'handle_base.dart'; +import 'object_handle.dart'; +import 'query_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; +import 'rooted_handle.dart'; +import 'schema_handle.dart'; +import 'session_handle.dart'; +import 'subscription_set_handle.dart'; + +class RealmHandle extends HandleBase { + int _counter = 0; + + final Map> _children = {}; + + RealmHandle(Pointer pointer) : super(pointer, 24); + + RealmHandle.unowned(super.pointer) : super.unowned(); + + factory RealmHandle.open(Configuration config) { + final configHandle = ConfigHandle.from(config); + return RealmHandle(realmLib + .realm_open(configHandle.pointer) // + .raiseLastErrorIfNull()); + } + + int addChild(RootedHandleBase child) { + final id = _counter++; + _children[id] = WeakReference(child); + rootedHandleFinalizer.attach(this, FinalizationToken(this, id), detach: this); + return id; + } + + void removeChild(int id) { + final child = _children.remove(id); + if (child != null) { + final target = child.target; + if (target != null) { + rootedHandleFinalizer.detach(target); + } + } + } + + @override + void releaseCore() { + for (final child in _children.values.toList()) { + child.target?.release(); + } + } + + ObjectHandle createWithPrimaryKey(int classKey, Object? primaryKey) { + return using((arena) { + final realmValue = primaryKey.toNative(arena); + return ObjectHandle(realmLib.realm_object_create_with_primary_key(pointer, classKey, realmValue.ref), this); + }); + } + + ObjectHandle create(int classKey) { + return ObjectHandle(realmLib.realm_object_create(pointer, classKey), this); + } + + ObjectHandle getOrCreateWithPrimaryKey(int classKey, Object? primaryKey) { + return using((arena) { + final realmValue = primaryKey.toNative(arena); + final didCreate = arena(); + return ObjectHandle( + realmLib.realm_object_get_or_create_with_primary_key( + pointer, + classKey, + realmValue.ref, + didCreate, + ), + this, + ); + }); + } + + bool compact() { + return using((arena) { + final outDidCompact = arena(); + realmLib.realm_compact(pointer, outDidCompact).raiseLastErrorIfFalse(); + return outDidCompact.value; + }); + } + + void writeCopy(Configuration config) { + final configHandle = ConfigHandle.from(config); + realmLib.realm_convert_with_config(pointer, configHandle.pointer, false).raiseLastErrorIfFalse(); + } + + ResultsHandle queryClass(int classKey, String query, List args) { + return using((arena) { + final length = args.length; + final argsPointer = arena(length); + for (var i = 0; i < length; ++i) { + intoRealmQueryArg(args[i], argsPointer + i, arena); + } + final queryHandle = QueryHandle( + realmLib.realm_query_parse( + pointer, + classKey, + query.toCharPtr(arena), + length, + argsPointer, + ), + this, + ); + return queryHandle.findAll(); + }); + } + + RealmHandle freeze() => RealmHandle(realmLib.realm_freeze(pointer)); + + SessionHandle getSession() { + return SessionHandle(realmLib.realm_sync_session_get(pointer), this); + } + + bool get isFrozen { + return realmLib.realm_is_frozen(pointer.cast()); + } + + SubscriptionSetHandle get subscriptions { + return SubscriptionSetHandle(realmLib.realm_sync_get_active_subscription_set(pointer), this); + } + + void disableAutoRefreshForTesting() { + realmLib.realm_set_auto_refresh(pointer, false); + } + + void close() { + realmLib.realm_close(pointer).raiseLastErrorIfFalse(); + } + + bool get isClosed { + return realmLib.realm_is_closed(pointer); + } + + void beginWrite() { + realmLib.realm_begin_write(pointer).raiseLastErrorIfFalse(); + } + + void commitWrite() { + realmLib.realm_commit(pointer).raiseLastErrorIfFalse(); + } + + Future beginWriteAsync(CancellationToken? ct) { + int? id; + final completer = CancellableCompleter(ct, onCancel: () { + if (id != null) { + _cancelAsync(id!); + } + }); + if (ct?.isCancelled != true) { + using((arena) { + final transactionId = arena(); + realmLib + .realm_async_begin_write( + pointer, + Pointer.fromFunction(_completeAsyncBeginWrite), + completer.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + true, + transactionId, + ) + .raiseLastErrorIfFalse(); + id = transactionId.value; + }); + } + return completer.future; + } + + Future commitWriteAsync(CancellationToken? ct) { + int? id; + final completer = CancellableCompleter(ct, onCancel: () { + if (id != null) { + _cancelAsync(id!); + } + }); + if (ct?.isCancelled != true) { + using((arena) { + final transactionId = arena(); + realmLib + .realm_async_commit( + pointer, + Pointer.fromFunction(_completeAsyncCommit), + completer.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + false, + transactionId, + ) + .raiseLastErrorIfFalse(); + id = transactionId.value; + }); + } + return completer.future; + } + + bool _cancelAsync(int cancellationId) { + return using((arena) { + final didCancel = arena(); + realmLib.realm_async_cancel(pointer, cancellationId, didCancel).raiseLastErrorIfFalse(); + return didCancel.value; + }); + } + + static void _completeAsyncBeginWrite(Pointer userdata) { + final Completer completer = userdata.toObject(); + completer.complete(); + } + + static void _completeAsyncCommit(Pointer userdata, bool error, Pointer description) { + final Completer completer = userdata.toObject(); + if (error) { + completer.completeError(RealmException(description.cast().toDartString())); + } else { + completer.complete(); + } + } + + bool get isWritable { + return realmLib.realm_is_writable(pointer); + } + + void rollbackWrite() { + realmLib.realm_rollback(pointer).raiseLastErrorIfFalse(); + } + + bool refresh() { + return using((arena) { + final didRefresh = arena(); + realmLib.realm_refresh(pointer, didRefresh).raiseLastErrorIfFalse(); + return didRefresh.value; + }); + } + + Future refreshAsync() async { + final completer = Completer(); + final callback = Pointer.fromFunction)>(_realmRefreshAsyncCallback); + final completerPtr = realmLib.realm_dart_object_to_persistent_handle(completer); + final result = realmLib.realm_add_realm_refresh_callback(pointer, callback.cast(), completerPtr, realmLib.addresses.realm_dart_delete_persistent_handle); + + if (result == nullptr) { + return false; + } + return await completer.future; + } + + static void _realmRefreshAsyncCallback(Pointer userdata) { + if (userdata == nullptr) { + return; + } + + final completer = realmLib.realm_dart_persistent_handle_to_object(userdata) as Completer; + completer.complete(true); + } + + ResultsHandle findAll(int classKey) { + return ResultsHandle(realmLib.realm_object_find_all(pointer, classKey), this); + } + + ObjectHandle? find(int classKey, Object? primaryKey) { + return using((arena) { + final realmValue = primaryKey.toNative(arena); + final found = arena(); + final ptr = realmLib.realm_object_find_with_primary_key(pointer, classKey, realmValue.ref, found); + if (!found.value) { + assert(ptr == nullptr); // If not found, the pointer should be null. Otherwise we have a leak + return null; + } + return ObjectHandle(ptr, this); + }); + } + + ObjectHandle? findExisting(int classKey, ObjectHandle other) { + final key = realmLib.realm_object_get_key(other.pointer); + return ObjectHandle(realmLib.realm_get_object(pointer, classKey, key), this); + } + + void renameProperty(String objectType, String oldName, String newName, SchemaHandle schema) { + using((arena) { + realmLib + .realm_schema_rename_property( + pointer, + schema.pointer, + objectType.toCharPtr(arena), + oldName.toCharPtr(arena), + newName.toCharPtr(arena), + ) + .raiseLastErrorIfFalse(); + }); + } + + bool deleteType(String objectType) { + return using((arena) { + final tableDeleted = arena(); + realmLib.realm_remove_table(pointer, objectType.toCharPtr(arena), tableDeleted).raiseLastErrorIfFalse(); + return tableDeleted.value; + }); + } + + ObjectHandle getObject(int classKey, int objectKey) { + return ObjectHandle(realmLib.realm_get_object(pointer, classKey, objectKey), this); + } + + CallbackTokenHandle subscribeForSchemaNotifications(Realm realm) { + return CallbackTokenHandle( + realmLib.realm_add_schema_changed_callback( + pointer, + Pointer.fromFunction(_schemaChangeCallback), + realm.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + ), + this, + ); + } + + RealmSchema readSchema() { + return using((arena) { + return _readSchema(arena); + }); + } + + RealmSchema _readSchema(Arena arena, {int expectedSize = 10}) { + final classesPtr = arena(expectedSize); + final actualCount = arena(); + realmLib.realm_get_class_keys(pointer, classesPtr, expectedSize, actualCount).raiseLastErrorIfFalse(); + if (expectedSize < actualCount.value) { + arena.free(classesPtr); + return _readSchema(arena, expectedSize: actualCount.value); + } + + final schemas = []; + for (var i = 0; i < actualCount.value; i++) { + final classInfo = arena(); + final classKey = (classesPtr + i).value; + realmLib.realm_get_class(pointer, classKey, classInfo).raiseLastErrorIfFalse(); + + final name = classInfo.ref.name.cast().toDartString(); + final baseType = ObjectType.values.firstWhere((element) => element.flags == classInfo.ref.flags, + orElse: () => throw RealmError('No object type found for flags ${classInfo.ref.flags}')); + final schema = _getSchemaForClassKey(classKey, name, baseType, arena, expectedSize: classInfo.ref.num_properties + classInfo.ref.num_computed_properties); + schemas.add(schema); + } + + return RealmSchema(schemas); + } + + SchemaObject _getSchemaForClassKey(int classKey, String name, ObjectType baseType, Arena arena, {int expectedSize = 10}) { + final actualCount = arena(); + final propertiesPtr = arena(expectedSize); + realmLib.realm_get_class_properties(pointer, classKey, propertiesPtr, expectedSize, actualCount).raiseLastErrorIfFalse(); + + if (expectedSize < actualCount.value) { + // The supplied array was too small - resize it + arena.free(propertiesPtr); + return _getSchemaForClassKey(classKey, name, baseType, arena, expectedSize: actualCount.value); + } + + final result = []; + for (var i = 0; i < actualCount.value; i++) { + final property = (propertiesPtr + i).ref.toSchemaProperty(); + result.add(property); + } + + late Type type; + switch (baseType) { + case ObjectType.realmObject: + type = RealmObject; + break; + case ObjectType.embeddedObject: + type = EmbeddedObject; + break; + case ObjectType.asymmetricObject: + type = AsymmetricObject; + break; + default: + throw RealmError('$baseType is not supported yet'); + } + + return SchemaObject(baseType, type, name, result); + } + + Map getPropertiesMetadata(int classKey, String? primaryKeyName) { + return using((arena) { + return _getPropertiesMetadata(classKey, primaryKeyName, arena); + }); + } + + RealmObjectMetadata getObjectMetadata(SchemaObject schema) { + return using((arena) { + final found = arena(); + final classInfo = arena(); + realmLib.realm_find_class(pointer, schema.name.toCharPtr(arena), found, classInfo).raiseLastErrorIfFalse(); + final primaryKey = classInfo.ref.primary_key.cast().toRealmDartString(treatEmptyAsNull: true); + return RealmObjectMetadata(schema, classInfo.ref.key, _getPropertiesMetadata(classInfo.ref.key, primaryKey, arena)); + }); + } + + Map _getPropertiesMetadata(int classKey, String? primaryKeyName, Arena arena) { + final propertyCountPtr = arena(); + realmLib.realm_get_property_keys(pointer, classKey, nullptr, 0, propertyCountPtr).raiseLastErrorIfFalse(); + + var propertyCount = propertyCountPtr.value; + final propertiesPtr = arena(propertyCount); + realmLib.realm_get_class_properties(pointer, classKey, propertiesPtr, propertyCount, propertyCountPtr).raiseLastErrorIfFalse(); + + propertyCount = propertyCountPtr.value; + Map result = {}; + for (var i = 0; i < propertyCount; i++) { + final property = propertiesPtr + i; + final propertyName = property.ref.name.cast().toRealmDartString()!; + final objectType = property.ref.link_target.cast().toRealmDartString(treatEmptyAsNull: true); + final linkOriginProperty = property.ref.link_origin_property_name.cast().toRealmDartString(treatEmptyAsNull: true); + final isNullable = property.ref.flags & realm_property_flags.RLM_PROPERTY_NULLABLE != 0; + final isPrimaryKey = propertyName == primaryKeyName; + final propertyMeta = RealmPropertyMetadata(property.ref.key, objectType, linkOriginProperty, RealmPropertyType.values.elementAt(property.ref.type), + isNullable, isPrimaryKey, RealmCollectionType.values.elementAt(property.ref.collection_type)); + result[propertyName] = propertyMeta; + } + return result; + } +} + +class CallbackTokenHandle extends RootedHandleBase { + CallbackTokenHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 32); +} + +void _schemaChangeCallback(Pointer userdata, Pointer data) { + final Realm realm = userdata.toObject(); + try { + realm.updateSchema(); + } catch (e) { + Realm.logger.log(LogLevel.error, 'Failed to update Realm schema: $e'); + } +} diff --git a/packages/realm_dart/lib/src/native/realm_library.dart b/packages/realm_dart/lib/src/native/realm_library.dart new file mode 100644 index 000000000..678c395b1 --- /dev/null +++ b/packages/realm_dart/lib/src/native/realm_library.dart @@ -0,0 +1,24 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'ffi.dart'; +import 'package:realm_common/realm_common.dart'; +import 'package:realm_dart/src/init.dart'; +import 'package:realm_dart/src/native/realm_bindings.dart'; + +const bugInTheSdkMessage = "This is likely a bug in the Realm SDK - please file an issue at https://github.com/realm/realm-dart/issues"; + +// stamped into the library by the build system (see prepare-release.yml) +const libraryVersion = '2.3.0'; + +final realmLib = () { + final result = RealmLibrary(initRealm()); + final nativeLibraryVersion = result.realm_dart_library_version().cast().toDartString(); + if (libraryVersion != nativeLibraryVersion) { + final additionMessage = + isFlutterPlatform ? bugInTheSdkMessage : "Did you forget to run `dart run realm_dart install` after upgrading the realm_dart package?"; + throw RealmError('Realm SDK package version does not match the native library version ($libraryVersion != $nativeLibraryVersion). $additionMessage'); + } + result.realm_dart_init_debug_logger(); + return result; +}(); diff --git a/packages/realm_dart/lib/src/native/results_handle.dart b/packages/realm_dart/lib/src/native/results_handle.dart new file mode 100644 index 000000000..0f4277776 --- /dev/null +++ b/packages/realm_dart/lib/src/native/results_handle.dart @@ -0,0 +1,117 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../realm_dart.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'notification_token_handle.dart'; +import 'object_handle.dart'; +import 'query_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'rooted_handle.dart'; + +class ResultsHandle extends RootedHandleBase { + ResultsHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 872); + + ResultsHandle queryResults(String query, List args) { + return using((arena) { + final length = args.length; + final argsPointer = arena(length); + for (var i = 0; i < length; ++i) { + intoRealmQueryArg(args[i], argsPointer + i, arena); + } + final queryHandle = QueryHandle( + realmLib.realm_query_parse_for_results( + pointer, + query.toCharPtr(arena), + length, + argsPointer, + ), + root, + ); + return queryHandle.findAll(); + }); + } + + int find(Object? value) { + return using((arena) { + final outIndex = arena(); + final outFound = arena(); + + // TODO: how should this behave for collections + final realmValue = value.toNative(arena); + realmLib + .realm_results_find( + pointer, + realmValue, + outIndex, + outFound, + ) + .raiseLastErrorIfFalse(); + return outFound.value ? outIndex.value : -1; + }); + } + + ObjectHandle getObjectAt(int index) { + return ObjectHandle(realmLib.realm_results_get_object(pointer, index), root); + } + + int get count { + return using((arena) { + final countPtr = arena(); + realmLib.realm_results_count(pointer, countPtr).raiseLastErrorIfFalse(); + return countPtr.value; + }); + } + + bool isValid() { + return using((arena) { + final isValid = arena(); + realmLib.realm_results_is_valid(pointer, isValid).raiseLastErrorIfFalse(); + return isValid.value; + }); + } + + void deleteAll() { + realmLib.realm_results_delete_all(pointer).raiseLastErrorIfFalse(); + } + + ResultsHandle snapshot() { + return ResultsHandle(realmLib.realm_results_snapshot(pointer), root); + } + + ResultsHandle resolveIn(RealmHandle realmHandle) { + return ResultsHandle(realmLib.realm_results_resolve_in(pointer, realmHandle.pointer), realmHandle); + } + + Object? elementAt(Realm realm, int index) { + return using((arena) { + final realmValue = arena(); + realmLib.realm_results_get(pointer, index, realmValue).raiseLastErrorIfFalse(); + return realmValue.toDartValue( + realm, + () => realmLib.realm_results_get_list(pointer, index), + () => realmLib.realm_results_get_dictionary(pointer, index), + ); + }); + } + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { + return NotificationTokenHandle( + realmLib.realm_results_add_notification_callback( + pointer, + controller.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + nullptr, + Pointer.fromFunction(collectionChangeCallback), + ), + root, + ); + } +} diff --git a/packages/realm_dart/lib/src/native/rooted_handle.dart b/packages/realm_dart/lib/src/native/rooted_handle.dart new file mode 100644 index 000000000..810961885 --- /dev/null +++ b/packages/realm_dart/lib/src/native/rooted_handle.dart @@ -0,0 +1,41 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'handle_base.dart'; +import 'realm_handle.dart'; + +class FinalizationToken { + final WeakReference root; + final int id; + + FinalizationToken(RealmHandle handle, this.id) : root = WeakReference(handle); +} + +// This finalizer is intended to prevent the list of children in the RealmHandle +// from growing endlessly. It's not intended to replace the native finalizer which +// will free the actual resources owned by the handle. +final rootedHandleFinalizer = Finalizer((token) { + token.root.target?.removeChild(token.id); +}); + +abstract class RootedHandleBase extends HandleBase { + final RealmHandle root; + int? _id; + + bool get shouldRoot => root.isUnowned; + + RootedHandleBase(this.root, Pointer pointer, int size) : super(pointer, size) { + if (shouldRoot) { + _id = root.addChild(this); + } + } + + @override + void releaseCore() { + if (_id != null) { + root.removeChild(_id!); + } + } +} diff --git a/packages/realm_dart/lib/src/native/scheduler_handle.dart b/packages/realm_dart/lib/src/native/scheduler_handle.dart new file mode 100644 index 000000000..4ad210542 --- /dev/null +++ b/packages/realm_dart/lib/src/native/scheduler_handle.dart @@ -0,0 +1,22 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +class SchedulerHandle extends HandleBase { + SchedulerHandle._(Pointer pointer) : super(pointer, 24); + + factory SchedulerHandle(int isolateId, int sendPort) { + final schedulerPtr = realmLib.realm_dart_create_scheduler(isolateId, sendPort); + return SchedulerHandle._(schedulerPtr); + } + + void invoke(int workQueue) { + final queuePointer = Pointer.fromAddress(workQueue); + realmLib.realm_scheduler_perform_work(queuePointer); + } +} diff --git a/packages/realm_dart/lib/src/native/schema_handle.dart b/packages/realm_dart/lib/src/native/schema_handle.dart new file mode 100644 index 000000000..e11512d46 --- /dev/null +++ b/packages/realm_dart/lib/src/native/schema_handle.dart @@ -0,0 +1,90 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; +import 'package:realm_common/realm_common.dart'; + +import '../configuration.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; +import 'to_native.dart'; + +class SchemaHandle extends HandleBase { + SchemaHandle(Pointer pointer) : super(pointer, 24); + + SchemaHandle.unowned(super.pointer) : super.unowned(); + + factory SchemaHandle.from(Iterable schema) { + return using((arena) { + final classCount = schema.length; + + final schemaClasses = arena(classCount); + final schemaProperties = arena>(classCount); + + for (var i = 0; i < classCount; i++) { + final schemaObject = schema.elementAt(i); + final classInfo = (schemaClasses + i).ref; + final propertiesCount = schemaObject.length; + final computedCount = schemaObject.where((p) => p.isComputed).length; + final persistedCount = propertiesCount - computedCount; + + classInfo.name = schemaObject.name.toCharPtr(arena); + classInfo.primary_key = "".toCharPtr(arena); + classInfo.num_properties = persistedCount; + classInfo.num_computed_properties = computedCount; + classInfo.key = RLM_INVALID_CLASS_KEY; + classInfo.flags = schemaObject.baseType.flags; + + final properties = arena(propertiesCount); + + for (var j = 0; j < propertiesCount; j++) { + final schemaProperty = schemaObject[j]; + final propInfo = (properties + j).ref; + propInfo.name = schemaProperty.mapTo.toCharPtr(arena); + propInfo.public_name = (schemaProperty.mapTo != schemaProperty.name ? schemaProperty.name : '').toCharPtr(arena); + propInfo.link_target = (schemaProperty.linkTarget ?? "").toCharPtr(arena); + propInfo.link_origin_property_name = (schemaProperty.linkOriginProperty ?? "").toCharPtr(arena); + propInfo.type = schemaProperty.propertyType.index; + propInfo.collection_type = schemaProperty.collectionType.index; + propInfo.flags = realm_property_flags.RLM_PROPERTY_NORMAL; + + if (schemaProperty.optional) { + propInfo.flags |= realm_property_flags.RLM_PROPERTY_NULLABLE; + } + + switch (schemaProperty.indexType) { + case RealmIndexType.regular: + propInfo.flags |= realm_property_flags.RLM_PROPERTY_INDEXED; + break; + case RealmIndexType.fullText: + propInfo.flags |= realm_property_flags.RLM_PROPERTY_FULLTEXT_INDEXED; + break; + default: + break; + } + + if (schemaProperty.primaryKey) { + classInfo.primary_key = schemaProperty.mapTo.toCharPtr(arena); + propInfo.flags |= realm_property_flags.RLM_PROPERTY_PRIMARY_KEY; + } + } + + schemaProperties[i] = properties; + (schemaProperties + i).value = properties; + } + + return SchemaHandle(realmLib.realm_schema_new(schemaClasses, classCount, schemaProperties)); + }); + } +} + +// From realm.h. Currently not exported from the shared library +// ignore: unused_field, constant_identifier_names +const int RLM_INVALID_CLASS_KEY = 0x7FFFFFFF; +// ignore: unused_field, constant_identifier_names +const int RLM_INVALID_PROPERTY_KEY = -1; +// ignore: unused_field, constant_identifier_names +const int RLM_INVALID_OBJECT_KEY = -1; diff --git a/packages/realm_dart/lib/src/native/session_handle.dart b/packages/realm_dart/lib/src/native/session_handle.dart new file mode 100644 index 000000000..0d0119d22 --- /dev/null +++ b/packages/realm_dart/lib/src/native/session_handle.dart @@ -0,0 +1,164 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'package:cancellation_token/cancellation_token.dart'; +import 'ffi.dart'; + +import '../realm_dart.dart'; +import '../scheduler.dart'; +import '../session.dart'; +import 'convert_native.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'rooted_handle.dart'; +import 'user_handle.dart'; + +class SessionHandle extends RootedHandleBase { + @override + bool get shouldRoot => true; + + SessionHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 24); + + String get path { + return realmLib.realm_sync_session_get_file_path(pointer).cast().toRealmDartString()!; + } + + ConnectionState get connectionState { + final value = realmLib.realm_sync_session_get_connection_state(pointer); + return ConnectionState.values[value]; + } + + UserHandle get user { + return UserHandle(realmLib.realm_sync_session_get_user(pointer)); + } + + SessionState get state { + final value = realmLib.realm_sync_session_get_state(pointer); + return _convertCoreSessionState(value); + } + + SessionState _convertCoreSessionState(int value) { + switch (value) { + case 0: // RLM_SYNC_SESSION_STATE_ACTIVE + case 1: // RLM_SYNC_SESSION_STATE_DYING + return SessionState.active; + case 2: // RLM_SYNC_SESSION_STATE_INACTIVE + case 3: // RLM_SYNC_SESSION_STATE_WAITING_FOR_ACCESS_TOKEN + case 4: // RLM_SYNC_SESSION_STATE_PAUSED + return SessionState.inactive; + default: + throw Exception("Unexpected SessionState: $value"); + } + } + + void pause() { + realmLib.realm_sync_session_pause(pointer); + } + + void resume() { + realmLib.realm_sync_session_resume(pointer); + } + + void raiseError(int errorCode, bool isFatal) { + using((arena) { + final message = "Simulated session error".toCharPtr(arena); + realmLib.realm_sync_session_handle_error_for_testing(pointer, errorCode, message, isFatal); + }); + } + + Future waitForUpload([CancellationToken? cancellationToken]) { + final completer = CancellableCompleter(cancellationToken); + if (!completer.isCancelled) { + final callback = Pointer.fromFunction)>(_waitCompletionCallback); + final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + realmLib.realm_sync_session_wait_for_upload_completion( + pointer, + realmLib.addresses.realm_dart_sync_wait_for_completion_callback, + userdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ); + } + return completer.future; + } + + Future waitForDownload([CancellationToken? cancellationToken]) { + final completer = CancellableCompleter(cancellationToken); + if (!completer.isCancelled) { + final callback = Pointer.fromFunction)>(_waitCompletionCallback); + final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + realmLib.realm_sync_session_wait_for_download_completion( + pointer, + realmLib.addresses.realm_dart_sync_wait_for_completion_callback, + userdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ); + } + return completer.future; + } + + static void _waitCompletionCallback(Object userdata, Pointer errorCode) { + final completer = userdata as CancellableCompleter; + if (completer.isCancelled) { + return; + } + if (errorCode != nullptr) { + // Throw RealmException instead of RealmError to be recoverable by the user. + completer.completeError(RealmException(errorCode.toDart().toString())); + } else { + completer.complete(); + } + } + + SyncSessionNotificationTokenHandle subscribeForConnectionStateNotifications(SessionConnectionStateController controller) { + final callback = Pointer.fromFunction(_onConnectionStateChange); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + return SyncSessionNotificationTokenHandle( + realmLib.realm_sync_session_register_connection_state_change_callback( + pointer, + realmLib.addresses.realm_dart_sync_connection_state_changed_callback, + userdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ), + ); + } + + SyncSessionNotificationTokenHandle subscribeForProgressNotifications( + ProgressDirection direction, + ProgressMode mode, + SessionProgressNotificationsController controller, + ) { + final isStreaming = mode == ProgressMode.reportIndefinitely; + final callback = Pointer.fromFunction(syncProgressCallback); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + return SyncSessionNotificationTokenHandle( + realmLib.realm_sync_session_register_progress_notifier( + pointer, + realmLib.addresses.realm_dart_sync_progress_callback, + direction.index, + isStreaming, + userdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ), + ); + } +} + +class SyncSessionNotificationTokenHandle extends HandleBase { + SyncSessionNotificationTokenHandle(Pointer pointer) : super(pointer, 32); +} + +void _onConnectionStateChange(Object userdata, int oldState, int newState) { + final controller = userdata as SessionConnectionStateController; + + controller.onConnectionStateChange(ConnectionState.values[oldState], ConnectionState.values[newState]); +} + +void syncProgressCallback(Object userdata, int transferred, int transferable, double estimate) { + final controller = userdata as ProgressNotificationsController; + + controller.onProgress(transferred, transferable); +} diff --git a/packages/realm_dart/lib/src/native/set_handle.dart b/packages/realm_dart/lib/src/native/set_handle.dart new file mode 100644 index 000000000..6135ae620 --- /dev/null +++ b/packages/realm_dart/lib/src/native/set_handle.dart @@ -0,0 +1,131 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../realm_dart.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'notification_token_handle.dart'; +import 'query_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; +import 'rooted_handle.dart'; + +class SetHandle extends RootedHandleBase { + SetHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 96); + + ResultsHandle get asResults { + return ResultsHandle(realmLib.realm_set_to_results(pointer), root); + } + + ResultsHandle query(String query, List args) { + return using((arena) { + final length = args.length; + final argsPointer = arena(length); + for (var i = 0; i < length; ++i) { + intoRealmQueryArg(args[i], argsPointer + i, arena); + } + final queryHandle = QueryHandle( + realmLib.realm_query_parse_for_set( + pointer, + query.toCharPtr(arena), + length, + argsPointer, + ), + root, + ); + return queryHandle.findAll(); + }); + } + + bool insert(Object? value) { + return using((arena) { + final realmValue = value.toNative(arena); + final outIndex = arena(); + final outInserted = arena(); + realmLib.realm_set_insert(pointer, realmValue.ref, outIndex, outInserted).raiseLastErrorIfFalse(); + return outInserted.value; + }); + } + + // TODO: avoid taking the [realm] parameter + Object? elementAt(Realm realm, int index) { + return using((arena) { + final realmValue = arena(); + realmLib.realm_set_get(pointer, index, realmValue).raiseLastErrorIfFalse(); + final result = realmValue.toDartValue( + realm, + () => throw RealmException('Sets cannot contain collections'), + () => throw RealmException('Sets cannot contain collections'), + ); + return result; + }); + } + + bool find(Object? value) { + return using((arena) { + // TODO: how should this behave for collections + final realmValue = value.toNative(arena); + final outIndex = arena(); + final outFound = arena(); + realmLib.realm_set_find(pointer, realmValue.ref, outIndex, outFound).raiseLastErrorIfFalse(); + return outFound.value; + }); + } + + bool remove(Object? value) { + return using((arena) { + // TODO: do we support sets containing mixed collections + final realmValue = value.toNative(arena); + final outErased = arena(); + realmLib.realm_set_erase(pointer, realmValue.ref, outErased).raiseLastErrorIfFalse(); + return outErased.value; + }); + } + + void clear() { + realmLib.realm_set_clear(pointer).raiseLastErrorIfFalse(); + } + + int get size { + return using((arena) { + final outSize = arena(); + realmLib.realm_set_size(pointer, outSize).raiseLastErrorIfFalse(); + return outSize.value; + }); + } + + bool get isValid { + return realmLib.realm_set_is_valid(pointer); + } + + void deleteAll() { + realmLib.realm_set_remove_all(pointer).raiseLastErrorIfFalse(); + } + + SetHandle? resolveIn(RealmHandle frozenRealm) { + return using((arena) { + final resultPtr = arena>(); + realmLib.realm_set_resolve_in(pointer, frozenRealm.pointer, resultPtr).raiseLastErrorIfFalse(); + return resultPtr == nullptr ? null : SetHandle(resultPtr.value, root); + }); + } + + NotificationTokenHandle subscribeForNotifications(NotificationsController controller) { + return NotificationTokenHandle( + realmLib.realm_set_add_notification_callback( + pointer, + controller.toPersistentHandle(), + realmLib.addresses.realm_dart_delete_persistent_handle, + nullptr, + Pointer.fromFunction(collectionChangeCallback), + ), + root, + ); + } +} diff --git a/packages/realm_dart/lib/src/native/subscription_handle.dart b/packages/realm_dart/lib/src/native/subscription_handle.dart new file mode 100644 index 000000000..06eb4cf87 --- /dev/null +++ b/packages/realm_dart/lib/src/native/subscription_handle.dart @@ -0,0 +1,28 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import '../realm_class.dart'; +import 'from_native.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +class SubscriptionHandle extends HandleBase { + SubscriptionHandle(Pointer pointer) : super(pointer, 184); + + ObjectId get id => realmLib.realm_sync_subscription_id(pointer).toDart(); + + String? get name => realmLib.realm_sync_subscription_name(pointer).toDart(); + + String get objectClassName => realmLib.realm_sync_subscription_object_class_name(pointer).toDart()!; + + String get queryString => realmLib.realm_sync_subscription_query_string(pointer).toDart()!; + + DateTime get createdAt => realmLib.realm_sync_subscription_created_at(pointer).toDart(); + + DateTime get updatedAt => realmLib.realm_sync_subscription_updated_at(pointer).toDart(); + + bool equalTo(SubscriptionHandle other) => realmLib.realm_equals(pointer.cast(), other.pointer.cast()); +} diff --git a/packages/realm_dart/lib/src/native/subscription_set_handle.dart b/packages/realm_dart/lib/src/native/subscription_set_handle.dart new file mode 100644 index 000000000..a4eadae0a --- /dev/null +++ b/packages/realm_dart/lib/src/native/subscription_set_handle.dart @@ -0,0 +1,81 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:ffi'; + +import 'package:cancellation_token/cancellation_token.dart'; +import 'ffi.dart'; + +import '../realm_dart.dart'; +import '../scheduler.dart'; +import 'convert.dart'; +import 'convert_native.dart'; +import 'error_handling.dart'; +import 'mutable_subscription_set_handle.dart'; +import 'realm_bindings.dart'; +import 'realm_handle.dart'; +import 'realm_library.dart'; +import 'results_handle.dart'; +import 'rooted_handle.dart'; +import 'subscription_handle.dart'; + +class SubscriptionSetHandle extends RootedHandleBase { + @override + bool get shouldRoot => true; + + SubscriptionSetHandle(Pointer pointer, RealmHandle root) : super(root, pointer, 128); + + void refresh() => realmLib.realm_sync_subscription_set_refresh(pointer).raiseLastErrorIfFalse(); + + int get size => realmLib.realm_sync_subscription_set_size(pointer); + + Exception? get error { + final error = realmLib.realm_sync_subscription_set_error_str(pointer); + final message = error.cast().toRealmDartString(treatEmptyAsNull: true); + return message.convert(RealmException.new); + } + + SubscriptionHandle operator [](int index) => SubscriptionHandle(realmLib.realm_sync_subscription_at(pointer, index)); + + SubscriptionHandle? findByName(String name) { + return using((arena) { + final result = realmLib.realm_sync_find_subscription_by_name( + pointer, + name.toCharPtr(arena), + ); + return result.convert(SubscriptionHandle.new); + }); + } + + SubscriptionHandle? findByResults(ResultsHandle results) { + final result = realmLib.realm_sync_find_subscription_by_results( + pointer, + results.pointer, + ); + return result.convert(SubscriptionHandle.new); + } + + int get version => realmLib.realm_sync_subscription_set_version(pointer); + + SubscriptionSetState get state => SubscriptionSetState.values[realmLib.realm_sync_subscription_set_state(pointer)]; + + MutableSubscriptionSetHandle toMutable() => MutableSubscriptionSetHandle(realmLib.realm_sync_make_subscription_set_mutable(pointer), root); + + static void _stateChangeCallback(Object userdata, int state) { + final completer = userdata as CancellableCompleter; + if (!completer.isCancelled) { + completer.complete(SubscriptionSetState.values[state]); + } + } + + Future waitForStateChange(SubscriptionSetState notifyWhen, [CancellationToken? cancellationToken]) { + final completer = CancellableCompleter(cancellationToken); + if (!completer.isCancelled) { + final callback = Pointer.fromFunction(_stateChangeCallback); + final userdata = realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle.pointer); + realmLib.realm_sync_on_subscription_set_state_change_async(pointer, notifyWhen.index, + realmLib.addresses.realm_dart_sync_on_subscription_state_changed_callback, userdata.cast(), realmLib.addresses.realm_dart_userdata_async_free); + } + return completer.future; + } +} diff --git a/packages/realm_dart/lib/src/native/to_native.dart b/packages/realm_dart/lib/src/native/to_native.dart new file mode 100644 index 000000000..5497146fc --- /dev/null +++ b/packages/realm_dart/lib/src/native/to_native.dart @@ -0,0 +1,173 @@ +import 'dart:convert'; +import 'dart:ffi'; +import 'dart:typed_data'; + +import 'package:realm_dart/src/native/realm_library.dart'; + +import '../realm_dart.dart'; +import '../realm_object.dart'; +import 'decimal128.dart'; +import 'realm_bindings.dart'; + +extension ListEx on List { + Pointer toCharPtr(Allocator allocator) { + return toUint8Ptr(allocator).cast(); + } + + Pointer toUint8Ptr(Allocator allocator) { + final nativeSize = length + 1; + final result = allocator(nativeSize); + final Uint8List native = result.asTypedList(nativeSize); + native.setAll(0, this); // copy + native.last = 0; // zero terminate + return result.cast(); + } +} + +extension StringEx on String { + Pointer toCharPtr(Allocator allocator) { + final units = utf8.encode(this); + return units.toCharPtr(allocator).cast(); + } + + Pointer toRealmString(Allocator allocator) { + final realmString = allocator(); + final units = utf8.encode(this); + realmString.ref.data = units.toCharPtr(allocator).cast(); + realmString.ref.size = units.length; + return realmString; + } +} + +extension NullableObjectEx on Object? { + Pointer toNative(Allocator allocator) { + final self = this; + final realmValue = allocator(); + if (self is RealmValue && self.type.isCollection) { + throw RealmError("Don't use toNative if the value may contain collections. $bugInTheSdkMessage"); + } + _intoRealmValue(self, realmValue.ref, allocator); + return realmValue; + } +} + +extension RealmValueTypeEx on RealmValueType { + String toQueryArgString() { + return switch (this) { + RealmValueType.nullValue => 'null', + RealmValueType.boolean => 'bool', + RealmValueType.string => 'string', + RealmValueType.int => 'int', + RealmValueType.double => 'double', + RealmValueType.object => 'link', + RealmValueType.objectId => 'objectid', + RealmValueType.dateTime => 'date', + RealmValueType.decimal => 'decimal', + RealmValueType.uuid => 'uuid', + RealmValueType.binary => 'binary', + RealmValueType.list => 'array', + RealmValueType.map => 'object', + }; + } +} + +const int _microsecondsPerSecond = 1000 * 1000; +const int _nanosecondsPerMicrosecond = 1000; + +void intoRealmQueryArg(Object? value, Pointer realmQueryArg, Allocator allocator) { + if (value is Iterable) { + realmQueryArg.ref.nb_args = value.length; + realmQueryArg.ref.is_list = true; + realmQueryArg.ref.arg = allocator(value.length); + int i = 0; + for (var item in value) { + _intoRealmValue(item, realmQueryArg.ref.arg[i], allocator); + i++; + } + } else { + realmQueryArg.ref.arg = allocator(); + realmQueryArg.ref.nb_args = 1; + realmQueryArg.ref.is_list = false; + _intoRealmValueHack(value, realmQueryArg.ref.arg.ref, allocator); + } +} + +void _intoRealmValueHack(Object? value, realm_value realmValue, Allocator allocator) { + if (value is GeoShape) { + _intoRealmValue(value.toString(), realmValue, allocator); + } else if (value is RealmValueType) { + _intoRealmValue(value.toQueryArgString(), realmValue, allocator); + } else { + _intoRealmValue(value, realmValue, allocator); + } +} + +void _intoRealmValue(Object? value, realm_value realmValue, Allocator allocator) { + if (value == null) { + realmValue.type = realm_value_type.RLM_TYPE_NULL; + } else if (value is RealmObjectBase) { + // when converting a RealmObjectBase to realm_value.link we assume the object is managed + final link = value.handle.asLink; + realmValue.values.link.target = link.targetKey; + realmValue.values.link.target_table = link.classKey; + realmValue.type = realm_value_type.RLM_TYPE_LINK; + } else if (value is int) { + realmValue.values.integer = value; + realmValue.type = realm_value_type.RLM_TYPE_INT; + } else if (value is bool) { + realmValue.values.boolean = value; + realmValue.type = realm_value_type.RLM_TYPE_BOOL; + } else if (value is String) { + String string = value; + final units = utf8.encode(string); + final result = allocator(units.length); + final Uint8List nativeString = result.asTypedList(units.length); + nativeString.setAll(0, units); + realmValue.values.string.data = result.cast(); + realmValue.values.string.size = units.length; + realmValue.type = realm_value_type.RLM_TYPE_STRING; + } else if (value is double) { + realmValue.values.dnum = value; + realmValue.type = realm_value_type.RLM_TYPE_DOUBLE; + } else if (value is ObjectId) { + final bytes = value.bytes; + for (var i = 0; i < 12; i++) { + realmValue.values.object_id.bytes[i] = bytes[i]; + } + realmValue.type = realm_value_type.RLM_TYPE_OBJECT_ID; + } else if (value is Uuid) { + final bytes = value.bytes.asUint8List(); + for (var i = 0; i < 16; i++) { + realmValue.values.uuid.bytes[i] = bytes[i]; + } + realmValue.type = realm_value_type.RLM_TYPE_UUID; + } else if (value is DateTime) { + final microseconds = value.toUtc().microsecondsSinceEpoch; + final seconds = microseconds ~/ _microsecondsPerSecond; + int nanoseconds = _nanosecondsPerMicrosecond * (microseconds % _microsecondsPerSecond); + if (microseconds < 0 && nanoseconds != 0) { + nanoseconds = nanoseconds - _nanosecondsPerMicrosecond * _microsecondsPerSecond; + } + realmValue.values.timestamp.seconds = seconds; + realmValue.values.timestamp.nanoseconds = nanoseconds; + realmValue.type = realm_value_type.RLM_TYPE_TIMESTAMP; + } else if (value is Decimal128) { + realmValue.values.decimal128 = value.value; + realmValue.type = realm_value_type.RLM_TYPE_DECIMAL128; + } else if (value is Uint8List) { + realmValue.type = realm_value_type.RLM_TYPE_BINARY; + realmValue.values.binary.size = value.length; + realmValue.values.binary.data = allocator(value.length); + realmValue.values.binary.data.asTypedList(value.length).setAll(0, value); + } else if (value is RealmValue) { + if (value is List) { + realmValue.type = realm_value_type.RLM_TYPE_LIST; + } else if (value is Map) { + realmValue.type = realm_value_type.RLM_TYPE_DICTIONARY; + } else { + return _intoRealmValue(value.value, realmValue, allocator); + } + } else { + throw RealmException("Property type ${value.runtimeType} not supported"); + } +} diff --git a/packages/realm_dart/lib/src/native/user_handle.dart b/packages/realm_dart/lib/src/native/user_handle.dart new file mode 100644 index 000000000..1092a0143 --- /dev/null +++ b/packages/realm_dart/lib/src/native/user_handle.dart @@ -0,0 +1,354 @@ +// Copyright 2024 MongoDB, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import 'dart:async'; +import 'dart:convert'; +import 'dart:ffi'; + +import 'ffi.dart'; + +import '../credentials.dart'; +import '../realm_dart.dart'; +import '../scheduler.dart'; +import '../user.dart'; +import 'app_handle.dart'; +import 'convert.dart'; +import 'convert_native.dart'; +import 'credentials_handle.dart'; +import 'error_handling.dart'; +import 'handle_base.dart'; +import 'realm_bindings.dart'; +import 'realm_library.dart'; + +class UserHandle extends HandleBase { + UserHandle(Pointer pointer) : super(pointer, 24); + + AppHandle get app { + return realmLib.realm_user_get_app(pointer).convert(AppHandle.new) ?? + (throw RealmException('User does not have an associated app. This is likely due to the user being logged out.')); + } + + UserState get state { + final nativeUserState = realmLib.realm_user_get_state(pointer); + return UserState.values.fromIndex(nativeUserState); + } + + String get id { + final idPtr = realmLib.realm_user_get_identity(pointer).raiseLastErrorIfNull(); + final userId = idPtr.cast().toDartString(); + return userId; + } + + List get identities { + return using((arena) { + return _userGetIdentities(arena); + }); + } + + List _userGetIdentities(Arena arena, {int expectedSize = 2}) { + final actualCount = arena(); + final identitiesPtr = arena(expectedSize); + realmLib.realm_user_get_all_identities(pointer, identitiesPtr, expectedSize, actualCount).raiseLastErrorIfFalse(); + + if (expectedSize < actualCount.value) { + // The supplied array was too small - resize it + arena.free(identitiesPtr); + return _userGetIdentities(arena, expectedSize: actualCount.value); + } + + final result = []; + for (var i = 0; i < actualCount.value; i++) { + final identity = (identitiesPtr + i).ref; + + result.add(UserIdentityInternal.create( + identity.id.cast().toRealmDartString(freeRealmMemory: true)!, AuthProviderTypeInternal.getByValue(identity.provider_type))); + } + + return result; + } + + Future logOut() async { + realmLib.realm_user_log_out(pointer).raiseLastErrorIfFalse(); + } + + String? get deviceId { + final deviceId = realmLib.realm_user_get_device_id(pointer).raiseLastErrorIfNull(); + return deviceId.cast().toRealmDartString(treatEmptyAsNull: true, freeRealmMemory: true); + } + + UserProfile get profileData { + final data = realmLib.realm_user_get_profile_data(pointer).raiseLastErrorIfNull(); + final dynamic profileData = jsonDecode(data.cast().toRealmDartString(freeRealmMemory: true)!); + return UserProfile(profileData as Map); + } + + String get refreshToken { + final token = realmLib.realm_user_get_refresh_token(pointer).raiseLastErrorIfNull(); + return token.cast().toRealmDartString(freeRealmMemory: true)!; + } + + String get accessToken { + final token = realmLib.realm_user_get_access_token(pointer).raiseLastErrorIfNull(); + return token.cast().toRealmDartString(freeRealmMemory: true)!; + } + + String get path { + final syncConfigPtr = realmLib.realm_flx_sync_config_new(pointer).raiseLastErrorIfNull(); + try { + final path = realmLib.realm_app_sync_client_get_default_file_path_for_realm(syncConfigPtr, nullptr); + return path.cast().toRealmDartString(freeRealmMemory: true)!; + } finally { + realmLib.realm_release(syncConfigPtr.cast()); + } + } + + String? get customData { + final customDataPtr = realmLib.realm_user_get_custom_data(pointer); + return customDataPtr.cast().toRealmDartString(freeRealmMemory: true, treatEmptyAsNull: true); + } + + Future linkCredentials(AppHandle app, CredentialsHandle credentials) { + final completer = Completer(); + realmLib + .realm_app_link_user( + app.pointer, + pointer, + credentials.pointer, + realmLib.addresses.realm_dart_user_completion_callback, + createAsyncUserCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + return completer.future; + } + + Future createApiKey(AppHandle app, String name) { + return using((arena) { + final namePtr = name.toCharPtr(arena); + final completer = Completer(); + realmLib + .realm_app_user_apikey_provider_client_create_apikey( + app.pointer, + pointer, + namePtr, + realmLib.addresses.realm_dart_apikey_callback, + _createAsyncApikeyCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + + return completer.future; + }); + } + + Future fetchApiKey(AppHandle app, ObjectId id) { + return using((arena) { + final completer = Completer(); + final nativeId = id.toNative(arena); + realmLib + .realm_app_user_apikey_provider_client_fetch_apikey( + app.pointer, + pointer, + nativeId.ref, + realmLib.addresses.realm_dart_apikey_callback, + _createAsyncApikeyCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + + return completer.future; + }); + } + + Future> fetchAllApiKeys(AppHandle app) { + return using((arena) { + final completer = Completer>(); + realmLib + .realm_app_user_apikey_provider_client_fetch_apikeys( + app.pointer, + pointer, + realmLib.addresses.realm_dart_apikey_list_callback, + _createAsyncApikeyListCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + + return completer.future; + }); + } + + Future deleteApiKey(AppHandle app, ObjectId id) { + return using((arena) { + final completer = Completer(); + final nativeId = id.toNative(arena); + realmLib + .realm_app_user_apikey_provider_client_delete_apikey( + app.pointer, + pointer, + nativeId.ref, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + + return completer.future; + }); + } + + Future disableApiKey(AppHandle app, ObjectId objectId) { + return using((arena) { + final completer = Completer(); + final nativeId = objectId.toNative(arena); + + realmLib + .realm_app_user_apikey_provider_client_disable_apikey( + app.pointer, + pointer, + nativeId.ref, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + + return completer.future; + }); + } + + Future enableApiKey(AppHandle app, ObjectId objectId) { + return using((arena) { + final completer = Completer(); + final nativeId = objectId.toNative(arena); + + realmLib + .realm_app_user_apikey_provider_client_enable_apikey( + app.pointer, + pointer, + nativeId.ref, + realmLib.addresses.realm_dart_void_completion_callback, + createAsyncCallbackUserdata(completer), + realmLib.addresses.realm_dart_userdata_async_free, + ) + .raiseLastErrorIfFalse(); + + return completer.future; + }); + } + + UserNotificationTokenHandle subscribeForNotifications(UserNotificationsController controller) { + final callback = Pointer.fromFunction(_userChangeCallback); + final userdata = realmLib.realm_dart_userdata_async_new(controller, callback.cast(), scheduler.handle.pointer); + final notificationToken = realmLib.realm_sync_user_on_state_change_register_callback( + pointer, + realmLib.addresses.realm_dart_user_change_callback, + userdata.cast(), + realmLib.addresses.realm_dart_userdata_async_free, + ); + return UserNotificationTokenHandle(notificationToken); + } +} + +class UserNotificationTokenHandle extends HandleBase { + UserNotificationTokenHandle(Pointer pointer) : super(pointer, 32); +} + +void _userChangeCallback(Object userdata, int data) { + final controller = userdata as UserNotificationsController; + + controller.onUserChanged(); +} + +Pointer createAsyncUserCallbackUserdata(Completer completer) { + final callback = Pointer.fromFunction< + Void Function( + Pointer, + Pointer, + Pointer, + )>(_appUserCompletionCallback); + + final userdata = realmLib.realm_dart_userdata_async_new( + completer, + callback.cast(), + scheduler.handle.pointer, + ); + + return userdata.cast(); +} + +void _appUserCompletionCallback(Pointer userdata, Pointer user, Pointer error) { + final Completer completer = userdata.toObject(); + + if (error != nullptr) { + completer.completeWithAppError(error); + return; + } + + user = realmLib.realm_clone(user.cast()).cast(); // take an extra reference to the user object + if (user == nullptr) { + completer.completeError(RealmException("Error while cloning user object.")); + return; + } + + completer.complete(UserHandle(user.cast())); +} + +Pointer _createAsyncApikeyCallbackUserdata(Completer completer) { + final callback = Pointer.fromFunction< + Void Function( + Pointer, + Pointer, + Pointer, + )>(_appApiKeyCompletionCallback); + + final userdata = realmLib.realm_dart_userdata_async_new( + completer, + callback.cast(), + scheduler.handle.pointer, + ); + + return userdata.cast(); +} + +void _appApiKeyCompletionCallback(Pointer userdata, Pointer apiKey, Pointer error) { + final Completer completer = userdata.toObject(); + if (error != nullptr) { + completer.completeWithAppError(error); + return; + } + completer.complete(apiKey.ref.toDart()); +} + +Pointer _createAsyncApikeyListCallbackUserdata(Completer> completer) { + final callback = Pointer.fromFunction< + Void Function( + Pointer, + Pointer, + Size count, + Pointer, + )>(_appApiKeyArrayCompletionCallback); + + final userdata = realmLib.realm_dart_userdata_async_new( + completer, + callback.cast(), + scheduler.handle.pointer, + ); + + return userdata.cast(); +} + +void _appApiKeyArrayCompletionCallback(Pointer userdata, Pointer apiKey, int size, Pointer error) { + final Completer> completer = userdata.toObject(); + + if (error != nullptr) { + completer.completeWithAppError(error); + return; + } + + final result = []; + for (var i = 0; i < size; i++) { + result.add(apiKey[i].toDart()); + } + + completer.complete(result); +} diff --git a/packages/realm_dart/lib/src/realm_class.dart b/packages/realm_dart/lib/src/realm_class.dart index 58a1f4422..8ae287584 100644 --- a/packages/realm_dart/lib/src/realm_class.dart +++ b/packages/realm_dart/lib/src/realm_class.dart @@ -13,7 +13,15 @@ import 'configuration.dart'; import 'list.dart'; import 'logging.dart'; import 'map.dart'; +import 'native/async_open_task_handle.dart'; +import 'native/handle_base.dart'; +import 'native/list_handle.dart'; +import 'native/map_handle.dart'; +import 'native/notification_token_handle.dart'; +import 'native/object_handle.dart'; import 'native/realm_core.dart'; +import 'native/realm_handle.dart'; +import 'native/set_handle.dart'; import 'realm_object.dart'; import 'results.dart'; import 'scheduler.dart'; @@ -56,6 +64,7 @@ export 'app.dart' show AppException, App, MetadataPersistenceMode, AppConfigurat export 'collections.dart' show Move; export "configuration.dart" show + encryptionKeySize, AfterResetCallback, BeforeResetCallback, ClientResetCallback, @@ -84,7 +93,7 @@ export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges, ListExte export 'logging.dart' hide RealmLoggerInternal; export 'map.dart' show RealmMap, RealmMapChanges, RealmMapOfObject; export 'migration.dart' show Migration; -export 'native/realm_core.dart' show Decimal128; +export 'native/decimal128.dart' show Decimal128; export 'realm_object.dart' show AsymmetricObject, @@ -111,7 +120,7 @@ class Realm implements Finalizable { late final RealmMetadata _metadata; late final RealmHandle _handle; final bool _isInMigration; - late final RealmCallbackTokenHandle? _schemaCallbackHandle; + late final CallbackTokenHandle? _schemaCallbackHandle; final List> _schemaChangeListeners = []; /// An object encompassing this `Realm` instance's dynamic API. @@ -127,7 +136,7 @@ class Realm implements Finalizable { /// Gets a value indicating whether this [Realm] is frozen. Frozen Realms are immutable /// and will not update when writes are made to the database. - late final bool isFrozen = realmCore.isFrozen(this); + late final bool isFrozen = handle.isFrozen; /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); @@ -141,7 +150,7 @@ class Realm implements Finalizable { // schema, so even if we subscribe, we wouldn't be able to see the updates. if (config is FlexibleSyncConfiguration || config.schemaObjects.isEmpty) { // TODO: enable once https://github.com/realm/realm-core/issues/7426 is fixed. - // _schemaCallbackHandle = realmCore.subscribeForSchemaNotifications(this); + //_schemaCallbackHandle = handle!.subscribeForSchemaNotifications(this); _schemaCallbackHandle = null; } else { _schemaCallbackHandle = null; @@ -173,7 +182,7 @@ class Realm implements Finalizable { _ensureDirectory(config); - final asyncOpenHandle = realmCore.createRealmAsyncOpenTask(config); + final asyncOpenHandle = AsyncOpenTaskHandle.from(config); return await CancellableFuture.from(() async { if (cancellationToken != null && cancellationToken.isCancelled) { throw cancellationToken.exception!; @@ -188,17 +197,17 @@ class Realm implements Finalizable { late final RealmHandle realmHandle; try { - realmHandle = await realmCore.openRealmAsync(asyncOpenHandle, cancellationToken); + realmHandle = await asyncOpenHandle.openAsync(cancellationToken); return Realm._(config, realmHandle); } finally { await progressSubscription?.cancel(); } - }, cancellationToken, onCancel: () => realmCore.cancelOpenRealmAsync(asyncOpenHandle)); + }, cancellationToken, onCancel: () => asyncOpenHandle.cancel()); } static RealmHandle _openRealm(Configuration config) { _ensureDirectory(config); - return realmCore.openRealm(config); + return RealmHandle.open(config); } static void _ensureDirectory(Configuration config) { @@ -209,8 +218,8 @@ class Realm implements Finalizable { } void _populateMetadata() { - schema = config.schemaObjects.isNotEmpty ? RealmSchema(config.schemaObjects) : realmCore.readSchema(this); - _metadata = RealmMetadata._(schema.map((c) => realmCore.getObjectMetadata(this, c))); + schema = config.schemaObjects.isNotEmpty ? RealmSchema(config.schemaObjects) : handle.readSchema(); + _metadata = RealmMetadata._(schema.map((c) => handle.getObjectMetadata(c))); } /// Deletes all files associated with a `Realm` located at given [path] @@ -283,16 +292,16 @@ class Realm implements Finalizable { object.manage(this, handle, accessor, false); } - RealmObjectHandle _createObject(RealmObjectBase object, RealmObjectMetadata metadata, bool update) { + ObjectHandle _createObject(RealmObjectBase object, RealmObjectMetadata metadata, bool update) { final key = metadata.classKey; final primaryKey = metadata.primaryKey; if (primaryKey == null) { - return realmCore.createRealmObject(this, key); + return handle.create(key); } if (update) { - return realmCore.getOrCreateRealmObjectWithPrimaryKey(this, key, object.accessor.get(object, primaryKey)); + return _handle.getOrCreateWithPrimaryKey(key, object.accessor.get(object, primaryKey)); } - return realmCore.createRealmObjectWithPrimaryKey(this, key, object.accessor.get(object, primaryKey)); + return _handle.createWithPrimaryKey(key, object.accessor.get(object, primaryKey)); } /// Adds a collection [RealmObject]s to this `Realm`. @@ -317,7 +326,7 @@ class Realm implements Finalizable { _ensureManagedByThis(object, 'delete object from Realm'); - realmCore.deleteRealmObject(object); + object.handle.delete(); } /// Deletes many [RealmObject]s from this `Realm`. @@ -328,15 +337,15 @@ class Realm implements Finalizable { if (items is RealmResults) { _ensureManagedByThis(items, 'delete objects from Realm'); - realmCore.resultsDeleteAll(items); + items.handle.deleteAll(); } else if (items is ManagedRealmList) { _ensureManagedByThis(items, 'delete objects from Realm'); - realmCore.listDeleteAll(items); + items.handle.deleteAll(); } else if (items is ManagedRealmSet) { _ensureManagedByThis(items, 'delete objects from Realm'); - realmCore.realmSetRemoveAll(items); + items.handle.deleteAll(); } else { for (T realmObject in items) { delete(realmObject); @@ -345,7 +354,7 @@ class Realm implements Finalizable { } /// Checks whether the `Realm` is in write transaction. - bool get isInTransaction => realmCore.getIsWritable(this); + bool get isInTransaction => handle.isWritable; /// Synchronously calls the provided callback inside a write transaction. /// @@ -366,14 +375,14 @@ class Realm implements Finalizable { /// Begins a write transaction for this [Realm]. Transaction beginWrite() { - realmCore.beginWrite(this); + handle.beginWrite(); return Transaction._(this); } /// Asynchronously begins a write transaction for this [Realm]. You can supply a /// [CancellationToken] to cancel the operation. Future beginWriteAsync([CancellationToken? cancellationToken]) async { - await realmCore.beginWriteAsync(this, cancellationToken); + await handle.beginWriteAsync(cancellationToken); return Transaction._(this); } @@ -404,18 +413,18 @@ class Realm implements Finalizable { } _schemaCallbackHandle?.release(); - realmCore.closeRealm(this); + handle.close(); handle.release(); } /// Checks whether the `Realm` is closed. - bool get isClosed => _handle.released || realmCore.isRealmClosed(this); + bool get isClosed => _handle.released || handle.isClosed; /// Fast lookup for a [RealmObject] with the specified [primaryKey]. T? find(Object? primaryKey) { final metadata = _metadata.getByType(T); - final handle = realmCore.find(this, metadata.classKey, primaryKey); + final handle = _handle.find(metadata.classKey, primaryKey); if (handle == null) { return null; } @@ -430,7 +439,7 @@ class Realm implements Finalizable { /// The returned [RealmResults] allows iterating all the values without further filtering. RealmResults all() { final metadata = _metadata.getByType(T); - final handle = realmCore.findAll(this, metadata.classKey); + final handle = this.handle.findAll(metadata.classKey); return RealmResultsInternal.create(handle, this, metadata); } @@ -440,8 +449,11 @@ class Realm implements Finalizable { /// and [Predicate Programming Guide.](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/AdditionalChapters/Introduction.html#//apple_ref/doc/uid/TP40001789) RealmResults query(String query, [List args = const []]) { final metadata = _metadata.getByType(T); - final handle = realmCore.queryClass(this, metadata.classKey, query, args); - return RealmResultsInternal.create(handle, this, metadata); + return RealmResultsInternal.create( + _handle.queryClass(metadata.classKey, query, args), + this, + metadata, + ); } /// Deletes all [RealmObject]s of type `T` in the `Realm` @@ -463,7 +475,7 @@ class Realm implements Finalizable { return this; } - return Realm._(config, realmCore.freeze(this)); + return Realm._(config, handle.freeze()); } WeakReference? _subscriptions; @@ -477,8 +489,8 @@ class Realm implements Finalizable { var result = _subscriptions?.target; if (result == null || result.handle.released) { - result = SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); - realmCore.refreshSubscriptionSet(result); + result = SubscriptionSetInternal.create(this, handle.subscriptions); + result.handle.refresh(); _subscriptions = WeakReference(result); } @@ -497,7 +509,7 @@ class Realm implements Finalizable { var result = _syncSession?.target; if (result == null || result.handle.released) { - result = SessionInternal.create(realmCore.realmGetSession(this)); + result = SessionInternal.create(handle.getSession()); _syncSession = WeakReference(result); } @@ -509,7 +521,7 @@ class Realm implements Finalizable { bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! Realm) return false; - return realmCore.realmEquals(this, other); + return handle == other.handle; } /// The logger that will emit log messages from the database and sync operations. @@ -560,7 +572,7 @@ class Realm implements Finalizable { realm.syncSession.pause(); } try { - return realmCore.compact(realm); + return realm.handle.compact(); } finally { realm.close(); } @@ -580,7 +592,7 @@ class Realm implements Finalizable { throw RealmError("Copying a Realm is not allowed within a write transaction or during migration."); } - realmCore.writeCopy(this, config); + handle.writeCopy(config); } /// Update the `Realm` instance and outstanding objects to point to the most recent persisted version. @@ -590,7 +602,7 @@ class Realm implements Finalizable { /// Typically you don't need to call this method since Realm has auto-refresh built-in. /// Note that this may return `true` even if no data has actually changed. bool refresh() { - return realmCore.realmRefresh(this); + return handle.refresh(); } /// Returns a [Future] that will complete when the `Realm` is refreshed to the version which is the @@ -598,7 +610,7 @@ class Realm implements Finalizable { /// /// Note that this may return `true` even if no data has actually changed. Future refreshAsync() async { - return realmCore.realmRefreshAsync(this); + return handle.refreshAsync(); } /// Allows listening for schema changes on this Realm. Only dynamic and synchronized @@ -618,7 +630,7 @@ class Realm implements Finalizable { } void _updateSchema() { - final newSchema = realmCore.readSchema(this); + final newSchema = handle.readSchema(); for (final listener in _schemaChangeListeners) { listener.add(RealmSchemaChanges._(schema, newSchema)); } @@ -627,11 +639,11 @@ class Realm implements Finalizable { final existing = schema.firstWhereOrNull((s) => s.name == schemaObject.name); if (existing == null) { schema.add(schemaObject); - final meta = realmCore.getObjectMetadata(this, schemaObject); + final meta = handle.getObjectMetadata(schemaObject); metadata._add(meta); } else if (schemaObject.length > existing.length) { final existingMeta = metadata.getByName(schemaObject.name); - final propertyMeta = realmCore.getPropertiesMetadata(this, existingMeta.classKey, existingMeta.primaryKey); + final propertyMeta = handle.getPropertiesMetadata(existingMeta.classKey, existingMeta.primaryKey); for (final property in schemaObject) { final existingProp = existing.firstWhereOrNull((e) => e.mapTo == property.mapTo); if (existingProp == null) { @@ -642,6 +654,8 @@ class Realm implements Finalizable { } } } + + void disableAutoRefreshForTesting() => handle.disableAutoRefreshForTesting(); } /// Describes the schema changes that occurred on a Realm @@ -671,7 +685,7 @@ class Transaction { void commit() { final realm = _ensureOpen('commit'); - realmCore.commitWrite(realm); + realm.handle.commitWrite(); _closeTransaction(); } @@ -682,7 +696,7 @@ class Transaction { Future commitAsync([CancellationToken? cancellationToken]) async { final realm = _ensureOpen('commitAsync'); - await realmCore.commitWriteAsync(realm, cancellationToken); + await realm.handle.commitWriteAsync(cancellationToken); _closeTransaction(); } @@ -692,7 +706,7 @@ class Transaction { final realm = _ensureOpen('rollback'); if (!realm.isClosed) { - realmCore.rollbackWrite(realm); + realm.handle.rollbackWrite(); } _closeTransaction(); @@ -734,20 +748,20 @@ extension RealmInternal on Realm { return Realm._(config, handle, isInMigration); } - RealmObjectBase createObject(Type type, RealmObjectHandle handle, RealmObjectMetadata metadata) { + RealmObjectBase createObject(Type type, ObjectHandle handle, RealmObjectMetadata metadata) { final accessor = RealmCoreAccessor(metadata, _isInMigration); return RealmObjectInternal.create(type, this, handle, accessor); } - RealmList createList(RealmListHandle handle, RealmObjectMetadata? metadata) { + RealmList createList(ListHandle handle, RealmObjectMetadata? metadata) { return RealmListInternal.create(handle, this, metadata); } - RealmSet createSet(RealmSetHandle handle, RealmObjectMetadata? metadata) { + RealmSet createSet(SetHandle handle, RealmObjectMetadata? metadata) { return RealmSetInternal.create(handle, this, metadata); } - RealmMap createMap(RealmMapHandle handle, RealmObjectMetadata? metadata) { + RealmMap createMap(MapHandle handle, RealmObjectMetadata? metadata) { return RealmMapInternal.create(handle, this, metadata); } @@ -765,7 +779,7 @@ extension RealmInternal on Realm { RealmMetadata get metadata => _metadata; - void manageEmbedded(RealmObjectHandle handle, EmbeddedObject object, {bool update = false}) { + void manageEmbedded(ObjectHandle handle, EmbeddedObject object, {bool update = false}) { final metadata = _metadata.getByType(object.runtimeType); final accessor = RealmCoreAccessor(metadata, _isInMigration); @@ -775,8 +789,8 @@ extension RealmInternal on Realm { /// This should only be used for testing RealmResults allEmbedded() { final metadata = _metadata.getByType(T); - final handle = realmCore.findAll(this, metadata.classKey); - return RealmResultsInternal.create(handle, this, metadata); + final resultsHandle = handle.findAll(metadata.classKey); + return RealmResultsInternal.create(resultsHandle, this, metadata); } T? resolveObject(T object) { @@ -788,46 +802,46 @@ extension RealmInternal on Realm { throw RealmStateError("Can't resolve invalidated (deleted) objects"); } - final handle = realmCore.resolveObject(object, this); - if (handle == null) { + final newHandle = object.handle.resolveIn(handle); + if (newHandle == null) { return null; } final metadata = (object.accessor as RealmCoreAccessor).metadata; - return RealmObjectInternal.create(T, this, handle, RealmCoreAccessor(metadata, _isInMigration)) as T; + return RealmObjectInternal.create(T, this, newHandle, RealmCoreAccessor(metadata, _isInMigration)) as T; } RealmList? resolveList(ManagedRealmList list) { - final handle = realmCore.resolveList(list, this); - if (handle == null) { + final newHandle = list.handle.resolveIn(handle); + if (newHandle == null) { return null; } - return createList(handle, list.metadata); + return createList(newHandle, list.metadata); } RealmResults resolveResults(RealmResults results) { - final handle = realmCore.resolveResults(results, this); - return RealmResultsInternal.create(handle, this, results.metadata); + final newHandle = results.handle.resolveIn(handle); + return RealmResultsInternal.create(newHandle, this, results.metadata); } RealmSet? resolveSet(ManagedRealmSet set) { - final handle = realmCore.resolveSet(set, this); - if (handle == null) { + final newHandle = set.handle.resolveIn(handle); + if (newHandle == null) { return null; } - return createSet(handle, set.metadata); + return createSet(newHandle, set.metadata); } RealmMap? resolveMap(ManagedRealmMap map) { - final handle = realmCore.resolveMap(map, this); - if (handle == null) { + final newHandle = map.handle.resolveIn(handle); + if (newHandle == null) { return null; } - return createMap(handle, map.metadata); + return createMap(newHandle, map.metadata); } static MigrationRealm getMigrationRealm(Realm realm) => MigrationRealm._(realm); @@ -851,9 +865,9 @@ extension RealmInternal on Realm { /// @nodoc abstract class NotificationsController implements Finalizable { - RealmNotificationTokenHandle? handle; + NotificationTokenHandle? handle; - RealmNotificationTokenHandle subscribe(); + NotificationTokenHandle subscribe(); void onChanges(HandleBase changesHandle); void onError(RealmError error); @@ -950,7 +964,7 @@ class DynamicRealm { /// The returned [RealmResults] allows iterating all the values without further filtering. RealmResults all(String className) { final metadata = _realm._metadata.getByName(className); - final handle = realmCore.findAll(_realm, metadata.classKey); + final handle = _realm.handle.findAll(metadata.classKey); return RealmResultsInternal.create(handle, _realm, metadata); } @@ -958,7 +972,7 @@ class DynamicRealm { RealmObject? find(String className, Object primaryKey) { final metadata = _realm._metadata.getByName(className); - final handle = realmCore.find(_realm, metadata.classKey, primaryKey); + final handle = _realm.handle.find(metadata.classKey, primaryKey); if (handle == null) { return null; } @@ -971,19 +985,19 @@ class DynamicRealm { RealmObject create(String className, {Object? primaryKey}) { final metadata = _realm._metadata.getByName(className); - RealmObjectHandle handle; + ObjectHandle handle; if (metadata.primaryKey != null) { if (primaryKey == null) { throw RealmError("The class $className has primary key defined, but you didn't pass one"); } - handle = realmCore.createRealmObjectWithPrimaryKey(_realm, metadata.classKey, primaryKey); + handle = _realm._handle.createWithPrimaryKey(metadata.classKey, primaryKey); } else { if (primaryKey != null) { throw RealmError("The class $className doesn't have primary key defined, but you passed $primaryKey"); } - handle = realmCore.createRealmObject(_realm, metadata.classKey); + handle = _realm._handle.create(metadata.classKey); } final accessor = RealmCoreAccessor(metadata, _realm._isInMigration); @@ -1016,8 +1030,8 @@ typedef ProgressCallback = void Function(SyncProgress syncProgress); /// @nodoc class RealmAsyncOpenProgressNotificationsController implements ProgressNotificationsController { - final RealmAsyncOpenTaskHandle _handle; - RealmAsyncOpenTaskProgressNotificationTokenHandle? _tokenHandle; + final AsyncOpenTaskHandle _handle; + AsyncOpenTaskProgressNotificationTokenHandle? _tokenHandle; late final StreamController _streamController; RealmAsyncOpenProgressNotificationsController._(this._handle); @@ -1037,7 +1051,7 @@ class RealmAsyncOpenProgressNotificationsController implements ProgressNotificat throw RealmStateError("Progress subscription already started."); } - _tokenHandle = realmCore.realmAsyncOpenRegisterAsyncOpenProgressNotifier(_handle, this); + _tokenHandle = _handle.registerProgressNotifier(this); } void _stop() { diff --git a/packages/realm_dart/lib/src/realm_object.dart b/packages/realm_dart/lib/src/realm_object.dart index b65dd715a..2e43d28d1 100644 --- a/packages/realm_dart/lib/src/realm_object.dart +++ b/packages/realm_dart/lib/src/realm_object.dart @@ -9,7 +9,10 @@ import 'package:realm_common/realm_common.dart'; import 'configuration.dart'; import 'list.dart'; -import 'native/realm_core.dart'; +import 'native/handle_base.dart'; +import 'native/notification_token_handle.dart'; +import 'native/object_handle.dart'; +import 'native/realm_library.dart'; import 'realm_class.dart'; import 'results.dart'; import 'map.dart'; @@ -156,11 +159,11 @@ class RealmCoreAccessor implements RealmAccessor { if (propertyMeta.propertyType == RealmPropertyType.linkingObjects) { final sourceMeta = object.realm.metadata.getByName(propertyMeta.objectType!); final sourceProperty = sourceMeta[propertyMeta.linkOriginProperty!]; - final handle = realmCore.getBacklinks(object, sourceMeta.classKey, sourceProperty.key); + final handle = object.handle.getBacklinks(sourceMeta.classKey, sourceProperty.key); return RealmResultsInternal.create(handle, object.realm, sourceMeta); } - final handle = realmCore.getListProperty(object, propertyMeta.key); + final handle = object.handle.getList(propertyMeta.key); final listMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); if (propertyMeta.propertyType == RealmPropertyType.mixed) { @@ -184,7 +187,7 @@ class RealmCoreAccessor implements RealmAccessor { } return object.realm.createList(handle, listMetadata); case RealmCollectionType.set: - final handle = realmCore.getSetProperty(object, propertyMeta.key); + final handle = object.handle.getSet(propertyMeta.key); final setMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); if (setMetadata != null && _isTypeGenericObject()) { switch (setMetadata.schema.baseType) { @@ -201,7 +204,7 @@ class RealmCoreAccessor implements RealmAccessor { return object.realm.createSet(handle, setMetadata); case RealmCollectionType.map: - final handle = realmCore.getMapProperty(object, propertyMeta.key); + final handle = object.handle.getMap(propertyMeta.key); final mapMetadata = propertyMeta.objectType == null ? null : object.realm.metadata.getByName(propertyMeta.objectType!); if (propertyMeta.propertyType == RealmPropertyType.mixed) { @@ -225,9 +228,9 @@ class RealmCoreAccessor implements RealmAccessor { } return object.realm.createMap(handle, mapMetadata); default: - var value = realmCore.getProperty(object, propertyMeta.key); + var value = object.handle.getValue(object.realm, propertyMeta.key); - if (value is RealmObjectHandle) { + if (value is ObjectHandle) { final meta = object.realm.metadata; final typeName = propertyMeta.objectType; @@ -235,7 +238,7 @@ class RealmCoreAccessor implements RealmAccessor { late RealmObjectMetadata targetMetadata; if (propertyMeta.propertyType == RealmPropertyType.mixed) { - (type, targetMetadata) = meta.getByClassKey(realmCore.getClassKey(value)); + (type, targetMetadata) = meta.getByClassKey(value.classKey); } else { // If we have an object but the user called the API without providing a generic // arg, we construct a RealmObject since we don't know the type of the object. @@ -262,14 +265,14 @@ class RealmCoreAccessor implements RealmAccessor { final propertyMeta = metadata[name]; try { if (value is RealmValue && value.type.isCollection) { - realmCore.objectSetCollection(object, propertyMeta.key, value); + object.handle.setCollection(object.realm, propertyMeta.key, value); return; } if (value is RealmList) { - final handle = realmCore.getListProperty(object, propertyMeta.key); + final handle = object.handle.getList(propertyMeta.key); if (update) { - realmCore.listClear(handle); + handle.clear(); } for (var i = 0; i < value.length; i++) { @@ -280,9 +283,9 @@ class RealmCoreAccessor implements RealmAccessor { //TODO: set from ManagedRealmList is not supported yet if (value is RealmSet) { - final handle = realmCore.getSetProperty(object, propertyMeta.key); + final handle = object.handle.getSet(propertyMeta.key); if (update) { - realmCore.realmSetClear(handle); + handle.clear(); } // TODO: use realmSetAssign when available in C-API @@ -291,7 +294,7 @@ class RealmCoreAccessor implements RealmAccessor { for (var element in value) { object.realm.addUnmanagedRealmObjectFromValue(element, update); - final result = realmCore.realmSetInsert(handle, element); + final result = handle.insert(element); if (!result) { throw RealmException("Error while adding value $element in RealmSet"); } @@ -300,9 +303,9 @@ class RealmCoreAccessor implements RealmAccessor { } if (value is RealmMap) { - final handle = realmCore.getMapProperty(object, propertyMeta.key); + final handle = object.handle.getMap(propertyMeta.key); if (update) { - realmCore.mapClear(handle); + handle.clear(); } for (var kvp in value.entries) { @@ -316,7 +319,7 @@ class RealmCoreAccessor implements RealmAccessor { throw RealmError("Can't set an embedded object that is already managed"); } - final handle = realmCore.createEmbeddedObject(object, propertyMeta.key); + final handle = object.handle.createEmbedded(propertyMeta.key); object.realm.manageEmbedded(handle, value, update: update); return; } @@ -324,13 +327,13 @@ class RealmCoreAccessor implements RealmAccessor { object.realm.addUnmanagedRealmObjectFromValue(value, update); if (propertyMeta.isPrimaryKey && !isInMigration) { - final currentValue = realmCore.getProperty(object, propertyMeta.key); + final currentValue = object.handle.getValue(object.realm, propertyMeta.key); if (currentValue != value) { throw RealmException("Primary key cannot be changed (original value: '$currentValue', supplied value: '$value')"); } } - realmCore.setProperty(object, propertyMeta.key, value, isDefault); + object.handle.setValue(propertyMeta.key, value, isDefault); } on Exception catch (e) { throw RealmException("Error setting property ${metadata._realmObjectTypeName}.$name Error: $e"); } @@ -363,7 +366,7 @@ extension RealmEntityInternal on RealmEntity { /// [RealmObject] should not be used directly as it is part of the generated class hierarchy. ex: `MyClass extends _MyClass with RealmObject`. /// {@category Realm} mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizable { - RealmObjectHandle? _handle; + ObjectHandle? _handle; RealmAccessor _accessor = RealmValuesAccessor(); static final Map _factories = { // Register default factories for `RealmObject` and `RealmObject?`. Whenever the user @@ -454,10 +457,10 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab if (other is! RealmObjectBase) return false; if (!isManaged || !other.isManaged) return false; - return realmCore.objectEquals(this, other); + return handle == other.handle; } - late final int _managedHashCode = realmCore.objectGetHashCode(this); + late final int _managedHashCode = handle.hashCode; @override int get hashCode { @@ -474,7 +477,7 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab /// will throw an exception. /// The Object is not valid if its [Realm] is closed or object is deleted. /// Unmanaged objects are always considered valid. - bool get isValid => isManaged ? realmCore.objectIsValid(this) : true; + bool get isValid => isManaged ? handle.isValid : true; /// Allows listening for property changes on this Realm object. /// @@ -596,7 +599,7 @@ mixin RealmObjectBase on RealmEntity implements RealmObjectBaseMarker, Finalizab throw RealmError( "Property $T.$propertyName is a link property that links to ${sourceProperty.objectType} which is different from the type of the current object, which is $runtimeType."); } - final handle = realmCore.getBacklinks(this, sourceMeta.classKey, sourceProperty.key); + final handle = this.handle.getBacklinks(sourceMeta.classKey, sourceProperty.key); return RealmResultsInternal.create(handle, realm, sourceMeta); } @@ -634,9 +637,9 @@ extension EmbeddedObjectExtension on EmbeddedObject { return null; } - final parent = realmCore.getEmbeddedParent(this); - final (type, metadata) = realm.metadata.getByClassKey(parent.item2); - return realm.createObject(type, parent.item1, metadata); + final (parentHandle, classKey) = handle.parent; + final (type, metadata) = realm.metadata.getByClassKey(classKey); + return realm.createObject(type, parentHandle, metadata); } } @@ -649,7 +652,7 @@ extension RealmObjectInternal on RealmObjectBase { _handle?.keepAlive(); } - void manage(Realm realm, RealmObjectHandle handle, RealmCoreAccessor accessor, bool update) { + void manage(Realm realm, ObjectHandle handle, RealmCoreAccessor accessor, bool update) { if (_handle != null) { //most certainly a bug hence we throw an Error throw ArgumentError("Object is already managed"); @@ -665,7 +668,7 @@ extension RealmObjectInternal on RealmObjectBase { _accessor = accessor; } - static RealmObjectBase create(Type type, Realm realm, RealmObjectHandle handle, RealmCoreAccessor accessor) { + static RealmObjectBase create(Type type, Realm realm, ObjectHandle handle, RealmCoreAccessor accessor) { final object = RealmObjectBase.createObject(type, accessor.metadata); object._handle = handle; object._accessor = accessor; @@ -673,7 +676,7 @@ extension RealmObjectInternal on RealmObjectBase { return object; } - RealmObjectHandle get handle { + ObjectHandle get handle { if (_handle?.released == true) { throw RealmClosedError('Cannot access an object that belongs to a closed Realm'); } @@ -725,17 +728,17 @@ class UserCallbackException extends RealmException { /// Describes the changes in on a single RealmObject since the last time the notification callback was invoked. class RealmObjectChanges implements Finalizable { // ignore: unused_field - final RealmObjectChangesHandle _handle; + final ObjectChangesHandle _handle; /// The realm object being monitored for changes. final T object; /// `True` if the object was deleted. - bool get isDeleted => realmCore.getObjectChangesIsDeleted(_handle); + bool get isDeleted => _handle.isDeleted; /// The property names that have changed. List get properties { - final propertyKeys = realmCore.getObjectChangesProperties(_handle); + final propertyKeys = _handle.properties; return object.realm .getPropertyNames(object, propertyKeys) .map((e) => object.objectSchema.firstWhere((element) => element.mapTo == e || element.name == e).name) @@ -766,13 +769,14 @@ class RealmObjectNotificationsController extends Noti if (keyPaths.any((element) => element.isEmpty)) { throw RealmException("It is not allowed to have empty key paths."); } - realmCore.buildAndVerifyKeyPath(realmObject, keyPaths); + // throw early if the key paths are invalid + realmObject.handle.buildAndVerifyKeyPath(keyPaths); } } @override - RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeObjectNotifications(realmObject, this, keyPaths); + NotificationTokenHandle subscribe() { + return realmObject.handle.subscribeForNotifications(this, keyPaths); } Stream> createStream() { @@ -782,7 +786,7 @@ class RealmObjectNotificationsController extends Noti @override void onChanges(HandleBase changesHandle) { - if (changesHandle is! RealmObjectChangesHandle) { + if (changesHandle is! ObjectChangesHandle) { throw RealmError("Invalid changes handle. RealmObjectChangesHandle expected"); } diff --git a/packages/realm_dart/lib/src/results.dart b/packages/realm_dart/lib/src/results.dart index df92d8ef9..f4c669f8f 100644 --- a/packages/realm_dart/lib/src/results.dart +++ b/packages/realm_dart/lib/src/results.dart @@ -7,7 +7,11 @@ import 'dart:ffi'; import 'package:cancellation_token/cancellation_token.dart'; import 'collections.dart'; -import 'native/realm_core.dart'; +import 'native/collection_changes_handle.dart'; +import 'native/handle_base.dart'; +import 'native/notification_token_handle.dart'; +import 'native/object_handle.dart'; +import 'native/results_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; @@ -17,7 +21,7 @@ import 'realm_object.dart'; /// {@category Realm} class RealmResults extends Iterable with RealmEntity implements Finalizable { final RealmObjectMetadata? _metadata; - final RealmResultsHandle _handle; + final ResultsHandle _handle; final int _skipOffset; // to support skip efficiently final _supportsSnapshot = [] is List; @@ -28,7 +32,7 @@ class RealmResults extends Iterable with RealmEntity imple } /// Gets a value indicating whether this collection is still valid to use. - bool get isValid => realmCore.resultsIsValid(this); + bool get isValid => handle.isValid(); /// Returns the element of type `T` at the specified [index]. T operator [](int index) => elementAt(index); @@ -41,13 +45,13 @@ class RealmResults extends Iterable with RealmEntity imple throw RangeError.range(index, 0, length - 1); } - var value = realmCore.resultsGetElementAt(this, _skipOffset + index); + var value = handle.elementAt(realm, _skipOffset + index); - if (value is RealmObjectHandle) { + if (value is ObjectHandle) { late RealmObjectMetadata targetMetadata; late Type type; if (T == RealmValue) { - (type, targetMetadata) = realm.metadata.getByClassKey(realmCore.getClassKey(value)); + (type, targetMetadata) = realm.metadata.getByClassKey(value.classKey); } else { targetMetadata = _metadata!; type = T; @@ -80,7 +84,7 @@ class RealmResults extends Iterable with RealmEntity imple if (start < 0) start = 0; start += _skipOffset; - final index = realmCore.resultsFind(this, element); + final index = handle.find(element); return index < start ? -1 : index; // to align with dart list semantics } @@ -101,7 +105,7 @@ class RealmResults extends Iterable with RealmEntity imple Iterator get iterator { var results = this; if (_supportsSnapshot) { - final handle = realmCore.resultsSnapshot(this); + final handle = this.handle.snapshot(); results = RealmResultsInternal.create(handle, realm, _metadata, _skipOffset); } return _RealmResultsIterator(results); @@ -109,7 +113,7 @@ class RealmResults extends Iterable with RealmEntity imple /// The number of values in this `Results` collection. @override - int get length => realmCore.getResultsCount(this) - _skipOffset; + int get length => handle.count - _skipOffset; @override T get first { @@ -173,7 +177,7 @@ extension RealmResultsOfObject on RealmResults { /// /// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://www.mongodb.com/docs/realm/realm-query-language/) RealmResults query(String query, [List args = const []]) { - final handle = realmCore.queryResults(this, query, args); + final handle = this.handle.queryResults(query, args); return RealmResultsInternal.create(handle, realm, _metadata); } } @@ -279,7 +283,7 @@ extension RealmResultsInternal on RealmResults { _handle.keepAlive(); } - RealmResultsHandle get handle { + ResultsHandle get handle { if (_handle.released) { throw RealmClosedError('Cannot access Results that belongs to a closed Realm'); } @@ -289,7 +293,7 @@ extension RealmResultsInternal on RealmResults { RealmObjectMetadata get metadata => _metadata!; - static RealmResults create(RealmResultsHandle handle, Realm realm, RealmObjectMetadata? metadata, [int skip = 0]) => + static RealmResults create(ResultsHandle handle, Realm realm, RealmObjectMetadata? metadata, [int skip = 0]) => RealmResults._(handle, realm, metadata, skip); } @@ -312,8 +316,8 @@ class ResultsNotificationsController extends NotificationsCon ResultsNotificationsController(this.results); @override - RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeResultsNotifications(results, this); + NotificationTokenHandle subscribe() { + return results.handle.subscribeForNotifications(this); } Stream> createStream() { @@ -323,7 +327,7 @@ class ResultsNotificationsController extends NotificationsCon @override void onChanges(HandleBase changesHandle) { - if (changesHandle is! RealmCollectionChangesHandle) { + if (changesHandle is! CollectionChangesHandle) { throw RealmError("Invalid changes handle. RealmCollectionChangesHandle expected"); } diff --git a/packages/realm_dart/lib/src/scheduler.dart b/packages/realm_dart/lib/src/scheduler.dart index d87a389cd..8c60f1ebe 100644 --- a/packages/realm_dart/lib/src/scheduler.dart +++ b/packages/realm_dart/lib/src/scheduler.dart @@ -4,10 +4,10 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:isolate'; -import 'package:realm_dart/src/logging.dart'; -import 'native/realm_core.dart'; +import 'package:realm_dart/src/logging.dart'; +import 'native/scheduler_handle.dart'; import 'realm_class.dart'; final _receivePortFinalizer = Finalizer((p) => p.close()); @@ -34,7 +34,7 @@ class Scheduler { // these. _receivePort.handler = Zone.current.bindUnaryCallbackGuarded(_handle); final sendPort = _receivePort.sendPort; - handle = realmCore.createScheduler(Isolate.current.hashCode, sendPort.nativePort); + handle = SchedulerHandle(Isolate.current.hashCode, sendPort.nativePort); } void _handle(dynamic message) { @@ -45,7 +45,7 @@ class Scheduler { final text = message[2] as String; Realm.logger.raise((category: category, level: level, message: text)); } else if (message is int) { - realmCore.invokeScheduler(message); + handle.invoke(message); } else { Realm.logger.log(LogLevel.error, 'Unexpected Scheduler message type: ${message.runtimeType} - $message'); } diff --git a/packages/realm_dart/lib/src/session.dart b/packages/realm_dart/lib/src/session.dart index 8d1ade90b..4b547e40d 100644 --- a/packages/realm_dart/lib/src/session.dart +++ b/packages/realm_dart/lib/src/session.dart @@ -3,11 +3,11 @@ import 'dart:async'; import 'dart:ffi'; -import '../realm.dart'; -import 'native/realm_core.dart'; -import 'user.dart'; +import '../realm.dart'; import '../src/native/realm_bindings.dart'; +import 'native/session_handle.dart'; +import 'user.dart'; /// An object encapsulating a synchronization session. Sessions represent the /// communication between the client (and a local Realm file on disk), and the @@ -18,38 +18,38 @@ class Session implements Finalizable { final SessionHandle _handle; /// The on-disk path of the file backing the [Realm] this [Session] represents - String get realmPath => realmCore.sessionGetPath(this); + String get realmPath => handle.path; /// The session’s current state. This is different from [connectionState] since a /// session may be active, even if the connection is disconnected (e.g. due to the device /// being offline). - SessionState get state => realmCore.sessionGetState(this); + SessionState get state => handle.state; /// The session’s current connection state. This is the physical state of the connection /// and is different from the session's logical state, which is returned by [state]. - ConnectionState get connectionState => realmCore.sessionGetConnectionState(this); + ConnectionState get connectionState => handle.connectionState; /// The [User] that owns the [Realm] this [Session] is synchronizing. - User get user => UserInternal.create(realmCore.sessionGetUser(this)); + User get user => UserInternal.create(handle.user); Session._(this._handle); /// Pauses any synchronization with the server until the Realm is re-opened again /// after fully closing it or [resume] is called. - void pause() => realmCore.sessionPause(this); + void pause() => handle.pause(); /// Attempts to resume the session and enable synchronization with the server. /// All sessions are active by default and calling this method is only necessary /// if [pause] was called previously. - void resume() => realmCore.sessionResume(this); + void resume() => handle.resume(); /// Waits for the [Session] to finish all pending uploads. /// An optional [cancellationToken] can be used to cancel the wait operation. - Future waitForUpload([CancellationToken? cancellationToken]) => realmCore.sessionWaitForUpload(this, cancellationToken); + Future waitForUpload([CancellationToken? cancellationToken]) => handle.waitForUpload(cancellationToken); /// Waits for the [Session] to finish all pending downloads. /// An optional [cancellationToken] can be used to cancel the wait operation. - Future waitForDownload([CancellationToken? cancellationToken]) => realmCore.sessionWaitForDownload(this, cancellationToken); + Future waitForDownload([CancellationToken? cancellationToken]) => handle.waitForDownload(cancellationToken); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { @@ -113,9 +113,7 @@ extension SessionInternal on Session { return _handle; } - void raiseError(int errorCode, bool isFatal) { - realmCore.raiseError(this, errorCode, isFatal); - } + void raiseError(int errorCode, bool isFatal) => handle.raiseError(errorCode, isFatal); static SyncProgress createSyncProgress(int transferredBytes, int transferableBytes) => SyncProgress._(progressEstimate: SyncProgress._calculateProgress(transferred: transferredBytes, transferable: transferableBytes)); @@ -131,7 +129,7 @@ class SessionProgressNotificationsController implements ProgressNotificationsCon final ProgressDirection _direction; final ProgressMode _mode; - RealmSyncSessionConnectionStateNotificationTokenHandle? _tokenHandle; + SyncSessionNotificationTokenHandle? _tokenHandle; late final StreamController _streamController; SessionProgressNotificationsController(this._session, this._direction, this._mode); @@ -154,7 +152,7 @@ class SessionProgressNotificationsController implements ProgressNotificationsCon if (_tokenHandle != null) { throw RealmStateError("Session progress subscription already started."); } - _tokenHandle = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this); + _tokenHandle = _session.handle.subscribeForProgressNotifications(_direction, _mode, this); } void _stop() { @@ -167,7 +165,7 @@ class SessionProgressNotificationsController implements ProgressNotificationsCon class SessionConnectionStateController { final Session _session; late final StreamController _streamController; - RealmSyncSessionConnectionStateNotificationTokenHandle? _token; + SyncSessionNotificationTokenHandle? _token; SessionConnectionStateController(this._session); @@ -184,7 +182,7 @@ class SessionConnectionStateController { if (_token != null) { throw RealmStateError("Session connection state subscription already started"); } - _token = realmCore.sessionRegisterConnectionStateNotifier(_session, this); + _token = _session.handle.subscribeForConnectionStateNotifications(this); } void _stop() { diff --git a/packages/realm_dart/lib/src/set.dart b/packages/realm_dart/lib/src/set.dart index bcfe9d3f3..6ac396f32 100644 --- a/packages/realm_dart/lib/src/set.dart +++ b/packages/realm_dart/lib/src/set.dart @@ -8,7 +8,11 @@ import 'dart:ffi'; import 'package:collection/collection.dart' as collection; -import 'native/realm_core.dart'; +import 'native/collection_changes_handle.dart'; +import 'native/handle_base.dart'; +import 'native/notification_token_handle.dart'; +import 'native/object_handle.dart'; +import 'native/set_handle.dart'; import 'realm_class.dart'; import 'realm_object.dart'; import 'collections.dart'; @@ -121,7 +125,7 @@ class UnmanagedRealmSet extends collection.DelegatingSet w } class ManagedRealmSet with RealmEntity, SetMixin implements RealmSet { - final RealmSetHandle _handle; + final SetHandle _handle; ManagedRealmSet._(this._handle, Realm realm, this._metadata) { setRealm(realm); @@ -131,7 +135,7 @@ class ManagedRealmSet with RealmEntity, SetMixin implement late final RealmObjectMetadata? _metadata; @override - bool get isValid => realmCore.realmSetIsValid(this); + bool get isValid => handle.isValid; @override bool add(T value) { @@ -145,7 +149,7 @@ class ManagedRealmSet with RealmEntity, SetMixin implement realm.addUnmanagedRealmObjectFromValue(value, false); } - return realmCore.realmSetInsert(_handle, value); + return _handle.insert(value); } @override @@ -155,12 +159,12 @@ class ManagedRealmSet with RealmEntity, SetMixin implement } try { - var value = realmCore.realmSetGetElementAt(this, index); - if (value is RealmObjectHandle) { + var value = handle.elementAt(realm, index); + if (value is ObjectHandle) { late RealmObjectMetadata targetMetadata; late Type type; if (T == RealmValue) { - (type, targetMetadata) = realm.metadata.getByClassKey(realmCore.getClassKey(value)); + (type, targetMetadata) = realm.metadata.getByClassKey(value.classKey); } else { targetMetadata = _metadata!; // will be null for RealmValue, so defer until here type = T; @@ -187,7 +191,7 @@ class ManagedRealmSet with RealmEntity, SetMixin implement _ensureManagedByThis(element, "contains"); - return realmCore.realmSetFind(this, element); + return handle.find(element); } @override @@ -203,7 +207,7 @@ class ManagedRealmSet with RealmEntity, SetMixin implement _ensureManagedByThis(value, "remove"); - return realmCore.realmSetErase(this, value); + return handle.remove(value); } @override @@ -213,10 +217,10 @@ class ManagedRealmSet with RealmEntity, SetMixin implement Set toSet() => {...this}; @override - void clear() => realmCore.realmSetClear(_handle); + void clear() => _handle.clear(); @override - int get length => realmCore.realmSetSize(this); + int get length => handle.size; @override Stream> get changes { @@ -263,7 +267,7 @@ class ManagedRealmSet with RealmEntity, SetMixin implement } @override - RealmResults asResults() => RealmResultsInternal.create(realmCore.resultsFromSet(this), realm, _metadata); + RealmResults asResults() => RealmResultsInternal.create(handle.asResults, realm, _metadata); @override RealmSet freeze() { @@ -282,7 +286,7 @@ extension RealmSetInternal on RealmSet { RealmObjectMetadata? get metadata => asManaged()._metadata; - RealmSetHandle get handle { + SetHandle get handle { final result = asManaged()._handle; if (result.released) { throw RealmClosedError('Cannot access a RealmSet that belongs to a closed Realm'); @@ -291,8 +295,7 @@ extension RealmSetInternal on RealmSet { return result; } - static RealmSet create(RealmSetHandle handle, Realm realm, RealmObjectMetadata? metadata) => - ManagedRealmSet._(handle, realm, metadata); + static RealmSet create(SetHandle handle, Realm realm, RealmObjectMetadata? metadata) => ManagedRealmSet._(handle, realm, metadata); } class _RealmSetIterator implements Iterator { @@ -337,8 +340,8 @@ class RealmSetNotificationsController extends NotificationsCo RealmSetNotificationsController(this.set); @override - RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeSetNotifications(set, this); + NotificationTokenHandle subscribe() { + return set.handle.subscribeForNotifications(this); } Stream> createStream() { @@ -348,7 +351,7 @@ class RealmSetNotificationsController extends NotificationsCo @override void onChanges(HandleBase changesHandle) { - if (changesHandle is! RealmCollectionChangesHandle) { + if (changesHandle is! CollectionChangesHandle) { throw RealmError("Invalid changes handle. RealmCollectionChangesHandle expected"); } @@ -371,7 +374,7 @@ extension RealmSetOfObject on RealmSet { /// /// For more details about the syntax of the Realm Query Language, refer to the documentation: https://www.mongodb.com/docs/realm/realm-query-language/. RealmResults query(String query, [List arguments = const []]) { - final handle = realmCore.querySet(asManaged(), query, arguments); + final handle = asManaged().handle.query(query, arguments); return RealmResultsInternal.create(handle, realm, _metadata); } } diff --git a/packages/realm_dart/lib/src/subscription.dart b/packages/realm_dart/lib/src/subscription.dart index 48dd9f0ec..9b4a58eba 100644 --- a/packages/realm_dart/lib/src/subscription.dart +++ b/packages/realm_dart/lib/src/subscription.dart @@ -2,50 +2,53 @@ // SPDX-License-Identifier: Apache-2.0 import 'dart:core'; -import 'dart:collection'; import 'dart:ffi'; -import 'native/realm_core.dart'; +import 'native/convert.dart'; +import 'native/mutable_subscription_set_handle.dart'; +import 'native/subscription_handle.dart'; +import 'native/subscription_set_handle.dart'; import 'realm_class.dart'; +import 'results.dart'; /// A class representing a single query subscription. The server will continuously /// evaluate the query that the app subscribed to and will send data /// that matches it as well as remove data that no longer does. /// {@category Sync} -class Subscription implements Finalizable { +final class Subscription implements Finalizable { final SubscriptionHandle _handle; Subscription._(this._handle); - late final ObjectId _id = realmCore.subscriptionId(this); + late final ObjectId _id = _handle.id; /// Name of the [Subscription], if one was provided at creation time. - String? get name => realmCore.subscriptionName(this); + String? get name => _handle.name; /// Class name of objects the [Subscription] refers to. /// /// If your types are remapped using [MapTo], the value /// returned will be the mapped-to value - i.e. the one that Realm uses internally /// rather than the name of the generated Dart class. - String get objectClassName => realmCore.subscriptionObjectClassName(this); + String get objectClassName => _handle.objectClassName; /// Query string that describes the [Subscription]. /// /// Objects matched by the query will be sent to the device by the server. - String get queryString => realmCore.subscriptionQueryString(this); + String get queryString => _handle.queryString; /// Timestamp when this [Subscription] was created. - DateTime get createdAt => realmCore.subscriptionCreatedAt(this); + DateTime get createdAt => _handle.createdAt; /// Timestamp when this [Subscription] was last updated. - DateTime get updatedAt => realmCore.subscriptionUpdatedAt(this); + DateTime get updatedAt => _handle.updatedAt; @override // ignore: hash_and_equals bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! Subscription) return false; - return realmCore.subscriptionEquals(this, other); + return _handle.equalTo(other._handle); } } @@ -59,7 +62,7 @@ extension SubscriptionInternal on Subscription { ObjectId get id => _id; } -class _SubscriptionIterator implements Iterator { +final class _SubscriptionIterator implements Iterator { int _index = -1; final SubscriptionSet _subscriptions; @@ -112,29 +115,25 @@ enum SubscriptionSetState { /// Realm is an expensive operation server-side, even if there's very little data that needs /// downloading. /// {@category Sync} -abstract class SubscriptionSet with IterableMixin implements Finalizable { +sealed class SubscriptionSet with Iterable implements Finalizable { final Realm _realm; - SubscriptionSetHandle _handle; + SubscriptionSetHandle __handle; + SubscriptionSetHandle get _handle => __handle.nullPtrAsNull ?? (throw RealmClosedError('Cannot access a SubscriptionSet that belongs to a closed Realm')); + set _handle(SubscriptionSetHandle value) => __handle = value; - SubscriptionSet._(this._realm, this._handle); + SubscriptionSet._(this._realm, this.__handle); /// Finds an existing [Subscription] in this set by its query /// /// The [query] is represented by the corresponding [RealmResults] object. - Subscription? find(RealmResults query) { - final result = realmCore.findSubscriptionByResults(this, query); - return result == null ? null : Subscription._(result); - } + Subscription? find(RealmResults query) => _handle.findByResults(query.handle).convert(Subscription._); /// Finds an existing [Subscription] in this set by name. - Subscription? findByName(String name) { - final result = realmCore.findSubscriptionByName(this, name); - return result == null ? null : Subscription._(result); - } + Subscription? findByName(String name) => _handle.findByName(name).convert(Subscription._); Future _waitForStateChange(SubscriptionSetState state, [CancellationToken? cancellationToken]) async { - final result = await realmCore.waitForSubscriptionSetStateChange(this, state, cancellationToken); - realmCore.refreshSubscriptionSet(this); + final result = await _handle.waitForStateChange(state, cancellationToken); + _handle.refresh(); return result; } @@ -153,15 +152,15 @@ abstract class SubscriptionSet with IterableMixin implements Final } /// Returns the error if the subscription set is in the [SubscriptionSetState.error] state. - Exception? get error => realmCore.getSubscriptionSetError(this); + Exception? get error => _handle.error; @override - int get length => realmCore.getSubscriptionSetSize(this); + int get length => _handle.size; @override Subscription elementAt(int index) { RangeError.checkValidRange(index, null, length); - return Subscription._(realmCore.subscriptionAt(this, index)); + return Subscription._(_handle[index]); } /// Gets the [Subscription] at the specified index in the set. @@ -180,11 +179,11 @@ abstract class SubscriptionSet with IterableMixin implements Final void update(void Function(MutableSubscriptionSet mutableSubscriptions) action); /// Gets the version of the subscription set. - int get version => realmCore.subscriptionSetGetVersion(this); + int get version => _handle.version; /// Gets the state of the subscription set. SubscriptionSetState get state { - final state = realmCore.subscriptionSetGetState(this); + final state = _handle.state; switch (state) { case SubscriptionSetState._uncommitted: case SubscriptionSetState._bootstrapping: @@ -202,42 +201,36 @@ extension SubscriptionSetInternal on SubscriptionSet { _handle.keepAlive(); } - Realm get realm => _realm; - SubscriptionSetHandle get handle { - if (_handle.released) { - throw RealmClosedError('Cannot access a SubscriptionSet that belongs to a closed Realm'); - } - - return _handle; - } + SubscriptionSetHandle get handle => _handle; static SubscriptionSet create(Realm realm, SubscriptionSetHandle handle) => ImmutableSubscriptionSet._(realm, handle); } -class ImmutableSubscriptionSet extends SubscriptionSet { +final class ImmutableSubscriptionSet extends SubscriptionSet { ImmutableSubscriptionSet._(super.realm, super.handle) : super._(); @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { - final mutableSubscriptions = MutableSubscriptionSet._(realm, realmCore.subscriptionSetMakeMutable(this)); - final oldHandle = _handle; + final old = _handle; + final mutable = _handle.toMutable(); try { - action(mutableSubscriptions); - _handle = realmCore.subscriptionSetCommit(mutableSubscriptions); + action(MutableSubscriptionSet._(_realm, mutable)); + _handle = mutable.commit(); } finally { // Release as early as possible, as we cannot start new update, until this is released! - mutableSubscriptions._handle.release(); - oldHandle.release(); + mutable.release(); + old.release(); } } } /// A mutable view to a [SubscriptionSet]. Obtained by calling [SubscriptionSet.update]. /// {@category Sync} -class MutableSubscriptionSet extends SubscriptionSet { - final MutableSubscriptionSetHandle _handle; +final class MutableSubscriptionSet extends SubscriptionSet { + @override + MutableSubscriptionSetHandle get _handle => super._handle as MutableSubscriptionSetHandle; - MutableSubscriptionSet._(Realm realm, this._handle) : super._(realm, _handle); + MutableSubscriptionSet._(super.realm, MutableSubscriptionSetHandle super.handle) : super._(); @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { @@ -254,28 +247,21 @@ class MutableSubscriptionSet extends SubscriptionSet { /// If [update] is specified to `true`, then any existing query will be replaced. /// Otherwise a [RealmException] is thrown, in case of duplicates. /// {@category Sync} - Subscription add(RealmResults query, {String? name, bool update = false}) { - return Subscription._(realmCore.insertOrAssignSubscription(this, query, name, update)); - } + Subscription add(RealmResults query, {String? name, bool update = false}) => + Subscription._(_handle.insertOrAssignSubscription(query.handle, name, update)); /// Removes the [subscription] from the set, if it exists. - bool remove(Subscription subscription) { - return realmCore.eraseSubscriptionById(this, subscription); - } + bool remove(Subscription subscription) => _handle.erase(subscription._handle); /// Removes the [query] from the set, if it exists. - bool removeByQuery(RealmResults query) { - return realmCore.eraseSubscriptionByResults(this, query); - } + bool removeByQuery(RealmResults query) => _handle.eraseByResults(query.handle); /// Removes the subscription from the set that matches by [name], if it exists. - bool removeByName(String name) { - return realmCore.eraseSubscriptionByName(this, name); - } + bool removeByName(String name) => _handle.eraseByName(name); /// Removes the subscriptions from the set that matches by type, if it exists. bool removeByType() { - final name = realm.schema.singleWhere((e) => e.type == T).name; + final name = _realm.schema.singleWhere((e) => e.type == T).name; var result = false; for (var i = length - 1; i >= 0; i--) { // reverse iteration to avoid index shifting @@ -298,11 +284,7 @@ class MutableSubscriptionSet extends SubscriptionSet { } } } else { - realmCore.clearSubscriptionSet(this); + _handle.clear(); } } } - -extension MutableSubscriptionSetInternal on MutableSubscriptionSet { - MutableSubscriptionSetHandle get handle => _handle; -} diff --git a/packages/realm_dart/lib/src/user.dart b/packages/realm_dart/lib/src/user.dart index b1a43c2c1..2c8e0d6a4 100644 --- a/packages/realm_dart/lib/src/user.dart +++ b/packages/realm_dart/lib/src/user.dart @@ -5,9 +5,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; -import 'native/realm_core.dart'; +import 'app.dart'; +import 'credentials.dart'; +import 'native/user_handle.dart'; import 'realm_class.dart'; -import './app.dart'; /// Describes the changes to a [User] instance - for example when the access token is updated or the user state changes. /// Right now, this only conveys information that the user has changed, but in the future it will be enhanced by adding @@ -33,7 +34,7 @@ class User { App get app { // The _app field may be null when we're retrieving a user from the session // rather than from the app. - return _app ??= AppInternal.create(realmCore.userGetApp(_handle)); + return _app ??= AppInternal.create(_handle.app); } late final ApiKeyClient _apiKeys = ApiKeyClient._(this); @@ -61,49 +62,49 @@ class User { /// The current state of this [User]. UserState get state { - return realmCore.userGetState(this); + return handle.state; } /// Get this [User]'s id on MongoDB Atlas. String get id { - return realmCore.userGetId(this); + return handle.id; } /// Gets a collection of all identities associated with this [User]. List get identities { - return realmCore.userGetIdentities(this); + return handle.identities; } /// Removes the [User]'s local credentials. This will also close any associated Sessions. Future logOut() async { - return await realmCore.userLogOut(this); + return await handle.logOut(); } /// Gets an unique identifier for the current device. String? get deviceId { - return realmCore.userGetDeviceId(this); + return handle.deviceId; } /// Gets the profile information for this [User]. UserProfile get profile { - return realmCore.userGetProfileData(this); + return handle.profileData; } /// Gets the refresh token for this [User]. This is the user's credential for /// accessing [Atlas App Services](https://www.mongodb.com/docs/atlas/app-services/) and should be treated as sensitive data. String get refreshToken { - return realmCore.userGetRefreshToken(this); + return handle.refreshToken; } /// Gets the access token for this [User]. This is the user's credential for /// accessing [Atlas App Services](https://www.mongodb.com/docs/atlas/app-services/) and should be treated as sensitive data. String get accessToken { - return realmCore.userGetAccessToken(this); + return handle.accessToken; } /// The custom user data associated with this [User]. dynamic get customData { - final data = realmCore.userGetCustomData(this); + final data = handle.customData; if (data == null) { return null; } @@ -113,7 +114,7 @@ class User { /// Refreshes the custom data for a this [User]. Future refreshCustomData() async { - await realmCore.userRefreshCustomData(app, this); + await app.handle.refreshCustomData(handle); return customData; } @@ -135,7 +136,7 @@ class User { /// await user.linkCredentials(Credentials.emailPassword("username", "password")); /// ``` Future linkCredentials(Credentials credentials) async { - final userHandle = await realmCore.userLinkCredentials(app, this, credentials); + final userHandle = await handle.linkCredentials(app.handle, credentials.handle); return UserInternal.create(userHandle, app); } @@ -144,7 +145,7 @@ class User { bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! User) return false; - return realmCore.userEquals(this, other); + return handle == other.handle; } void _ensureLoggedIn([String clarification = 'perform this action']) { @@ -169,7 +170,7 @@ class UserNotificationsController implements Finalizable { throw RealmStateError("User notifications subscription already started"); } - tokenHandle = realmCore.subscribeUserNotifications(this); + tokenHandle = user.handle.subscribeForNotifications(this); } void stop() { @@ -275,42 +276,42 @@ class ApiKeyClient { Future create(String name) async { _user._ensureLoggedIn('create an API key'); - return realmCore.createApiKey(_user, name); + return _user.handle.createApiKey(_user.app.handle, name); } /// Fetches a specific API key by id. Future fetch(ObjectId id) { _user._ensureLoggedIn('fetch an API key'); - return realmCore.fetchApiKey(_user, id).handle404(); + return _user.handle.fetchApiKey(_user.app.handle, id).handle404(); } /// Fetches all API keys associated with the user. Future> fetchAll() async { _user._ensureLoggedIn('fetch all API keys'); - return realmCore.fetchAllApiKeys(_user); + return _user.handle.fetchAllApiKeys(_user.app.handle); } /// Deletes a specific API key by id. Future delete(ObjectId objectId) { _user._ensureLoggedIn('delete an API key'); - return realmCore.deleteApiKey(_user, objectId).handle404(); + return _user.handle.deleteApiKey(_user.app.handle, objectId).handle404(); } /// Disables an API key by id. Future disable(ObjectId objectId) { _user._ensureLoggedIn('disable an API key'); - return realmCore.disableApiKey(_user, objectId).handle404(id: objectId); + return _user.handle.disableApiKey(_user.app.handle, objectId).handle404(id: objectId); } /// Enables an API key by id. Future enable(ObjectId objectId) { _user._ensureLoggedIn('enable an API key'); - return realmCore.enableApiKey(_user, objectId).handle404(id: objectId); + return _user.handle.enableApiKey(_user.app.handle, objectId).handle404(id: objectId); } } @@ -354,7 +355,7 @@ class FunctionsClient { Future call(String name, [List functionArgs = const []]) async { _user._ensureLoggedIn('call Atlas function'); final args = jsonEncode(functionArgs); - final response = await realmCore.callAppFunction(_user.app, _user, name, args); + final response = await _user.app.handle.callAppFunction(_user.handle, name, args); return jsonDecode(response); } } diff --git a/packages/realm_dart/test/app_test.dart b/packages/realm_dart/test/app_test.dart index b543c0946..9dd41f46e 100644 --- a/packages/realm_dart/test/app_test.dart +++ b/packages/realm_dart/test/app_test.dart @@ -299,7 +299,7 @@ void main() { expect(app.currentUser, user2); expect( () => app.switchUser(user1), - throws("Switch user failed. User is no longer valid or is logged out"), + throws("User is no longer valid or is logged out"), ); }); diff --git a/packages/realm_dart/test/baas_helper.dart b/packages/realm_dart/test/baas_helper.dart index d35874125..52a5a368b 100644 --- a/packages/realm_dart/test/baas_helper.dart +++ b/packages/realm_dart/test/baas_helper.dart @@ -151,9 +151,9 @@ class BaasHelper { } } - Future createServerApiKey(App app, String name, {bool enabled = true}) async { + Future createServerApiKey(App app, String name, {bool enabled = true}) { final baasApp = _baasApps.values.firstWhere((ba) => ba.clientAppId == app.id); - return await _baasClient.createApiKey(baasApp.appId, name, enabled); + return _baasClient.createApiKey(baasApp.appId, name, enabled); } static void throwIfSetupFailed() { diff --git a/packages/realm_dart/test/configuration_test.dart b/packages/realm_dart/test/configuration_test.dart index aa512c948..5dbdf98eb 100644 --- a/packages/realm_dart/test/configuration_test.dart +++ b/packages/realm_dart/test/configuration_test.dart @@ -3,9 +3,10 @@ import 'dart:io'; import 'dart:math'; -import 'package:test/test.dart' hide test, throws; + import 'package:path/path.dart' as path; import 'package:realm_dart/realm.dart'; + import 'test.dart'; void main() { diff --git a/packages/realm_dart/test/decimal128_test.dart b/packages/realm_dart/test/decimal128_test.dart index ef6d76111..85aec4ca7 100644 --- a/packages/realm_dart/test/decimal128_test.dart +++ b/packages/realm_dart/test/decimal128_test.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:meta/meta.dart'; +import 'package:realm_dart/src/native/decimal128.dart'; import 'package:test/expect.dart' hide throws; import 'package:realm_dart/src/native/realm_core.dart'; diff --git a/packages/realm_dart/test/dynamic_realm_test.dart b/packages/realm_dart/test/dynamic_realm_test.dart index e5c218412..8d2580c5b 100644 --- a/packages/realm_dart/test/dynamic_realm_test.dart +++ b/packages/realm_dart/test/dynamic_realm_test.dart @@ -4,9 +4,7 @@ // ignore_for_file: avoid_relative_lib_imports import 'package:collection/collection.dart'; -import 'dart:ffi'; import 'dart:typed_data'; -import 'package:test/test.dart' hide test, throws; import 'package:realm_dart/realm.dart'; import 'test.dart'; diff --git a/packages/realm_dart/test/embedded_test.dart b/packages/realm_dart/test/embedded_test.dart index 431f32874..9936d7d67 100644 --- a/packages/realm_dart/test/embedded_test.dart +++ b/packages/realm_dart/test/embedded_test.dart @@ -1,6 +1,7 @@ // Copyright 2022 MongoDB, Inc. // SPDX-License-Identifier: Apache-2.0 +import 'package:realm_dart/src/native/decimal128.dart'; import 'package:test/test.dart' hide test, throws; import 'package:realm_dart/realm.dart'; diff --git a/packages/realm_dart/test/realm_test.dart b/packages/realm_dart/test/realm_test.dart index 6d5a6e6d6..d8c946499 100644 --- a/packages/realm_dart/test/realm_test.dart +++ b/packages/realm_dart/test/realm_test.dart @@ -9,7 +9,6 @@ import 'dart:isolate'; import 'package:path/path.dart' as p; import 'package:realm_dart/realm.dart'; import 'package:realm_dart/src/native/realm_core.dart'; -import 'package:test/test.dart' hide test, throws; import 'package:timezone/data/latest.dart' as tz; import 'package:timezone/timezone.dart' as tz; @@ -393,6 +392,9 @@ void main() { //Ensure the team exists in realm var teams = realm.all(); expect(teams.length, 1); + expect(teams[0].players, newPlayers); + final allPersons = realm.all(); + expect(allPersons.length, 3); //Delete team players realm.write(() => realm.deleteMany(teams[0].players)); @@ -401,7 +403,6 @@ void main() { expect(teams[0].players.length, 0); //Reload all persons from realm and ensure they are deleted - final allPersons = realm.all(); expect(allPersons.length, 0); }); @@ -1820,7 +1821,7 @@ void main() { final results = realm.query(r"name == $0", [personName]); expect(realm.refresh(), false); - realmCore.realmDisableAutoRefreshForTesting(realm); + realm.disableAutoRefreshForTesting(); ReceivePort receivePort = ReceivePort(); Isolate.spawn((SendPort sendPort) async { diff --git a/packages/realm_dart/test/realm_value_test.dart b/packages/realm_dart/test/realm_value_test.dart index 4b3b6b4ff..eb679f203 100644 --- a/packages/realm_dart/test/realm_value_test.dart +++ b/packages/realm_dart/test/realm_value_test.dart @@ -2454,7 +2454,8 @@ void main() { expect(RealmValue.from(bin1), isNot(RealmValue.from(bin2))); } - // Not quite 8 times speedup, but close enough. - expect(listEqualsClock.elapsedTicks ~/ 5, greaterThan(memEqualsClock.elapsedTicks)); + // Not quite 8 times speedup, but close enough. Setting the threshold to 4 + // for test stability sake. + expect(listEqualsClock.elapsedTicks ~/ 4, greaterThan(memEqualsClock.elapsedTicks)); }); } diff --git a/packages/realm_dart/test/test.dart b/packages/realm_dart/test/test.dart index b7682133e..7dc8eae1c 100644 --- a/packages/realm_dart/test/test.dart +++ b/packages/realm_dart/test/test.dart @@ -7,18 +7,20 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:math'; import 'dart:typed_data'; -import 'package:logging/logging.dart'; + import 'package:meta/meta.dart'; import 'package:path/path.dart' as _path; -import 'package:realm_dart/src/logging.dart'; -import 'package:test/test.dart' hide test; -import 'package:test/test.dart' as testing; import 'package:realm_dart/realm.dart'; -import 'package:realm_dart/src/native/realm_core.dart'; import 'package:realm_dart/src/configuration.dart'; +import 'package:realm_dart/src/logging.dart'; +import 'package:realm_dart/src/native/realm_core.dart'; +import 'package:realm_dart/src/realm_object.dart'; +import 'package:test/test.dart'; import 'baas_helper.dart'; +export 'package:test/test.dart'; + export 'baas_helper.dart' show AppName; part 'test.realm.dart'; @@ -407,33 +409,14 @@ final _openRealms = Queue(); String testUsername = "realm-test@realm.io"; String testPassword = "123456"; -final int encryptionKeySize = 64; const int maxInt = 9223372036854775807; const int minInt = -9223372036854775808; const int jsMaxInt = 9007199254740991; const int jsMinInt = -9007199254740991; -//Overrides test method so we can filter tests -void test(String name, dynamic Function() testFunction, {dynamic skip, Map? onPlatform}) { - if (testName != null && !name.contains(testName!)) { - return; - } - - var timeout = 60; - assert(() { - if (Platform.environment['CI'] == null) { - timeout = Duration(minutes: 5).inSeconds; - } - - return true; - }()); - - testing.test(name, testFunction, skip: skip, onPlatform: onPlatform, timeout: Timeout(Duration(seconds: timeout))); -} - void xtest(String? name, dynamic Function() testFunction, {dynamic skip, Map? onPlatform}) { - testing.test(name, testFunction, skip: "Test is disabled"); + test(name, testFunction, skip: "Test is disabled"); } BaasHelper? baasHelper; @@ -444,7 +427,7 @@ void setupTests() { Realm.logger.setLogLevel(LogLevel.detail); Realm.logger.onRecord.listen((record) { - testing.printOnFailure('${record.category} ${record.level.name}: ${record.message}'); + printOnFailure('${record.category} ${record.level.name}: ${record.message}'); }); // Enable this to print platform info, including current PID @@ -603,7 +586,7 @@ Future baasTest( baasHelper!.printSplunkLogLink(appName, baasHelper?.baseUrl); final config = await baasHelper!.getAppConfig(appName: appName); await testFunction(config); - }, skip: skip); + }, skip: skip, tags: 'baas'); } dynamic shouldSkip(dynamic skip) { @@ -692,7 +675,7 @@ Future waitForConditionWithResult(FutureOr Function() getter, FutureOr< } extension RealmObjectTest on RealmObjectBase { - String toJson() => realmCore.objectToString(this); + String toJson() => handle.objectToString(); } extension on int { diff --git a/packages/realm_generator/test/good_test_data/const_initializer.dart b/packages/realm_generator/test/good_test_data/const_initializer.dart index 9fcf7b601..fba9d4ca0 100644 --- a/packages/realm_generator/test/good_test_data/const_initializer.dart +++ b/packages/realm_generator/test/good_test_data/const_initializer.dart @@ -13,7 +13,7 @@ class _ConstInitializer { int minusMinusOne = -(-1); int add = 1 + 1; int identifier = myConst; - + double infinity = double.infinity; double nan = double.nan; double negativeInfinity = double.negativeInfinity;