From 00e58a2a0281c370d9e6744d4f77c8bf8e5cca2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 20 Apr 2022 18:11:00 +0200 Subject: [PATCH 001/122] Introduce hierarchy on Configurations --- lib/src/configuration.dart | 178 +++++++++++++++++++++++++-------- lib/src/native/realm_core.dart | 55 +++++----- 2 files changed, 163 insertions(+), 70 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index fdeaa2929..71f31ab12 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -18,36 +18,17 @@ import 'dart:io'; -import 'native/realm_core.dart'; - -import 'realm_object.dart'; -import 'realm_property.dart'; import 'package:path/path.dart' as _path; -import 'realm_class.dart'; -/// Configuration used to create a [Realm] instance -/// {@category Configuration} -class Configuration { - static String? _defaultPath; - - /// The [RealmSchema] for this [Configuration] - final RealmSchema schema; +import 'native/realm_core.dart'; +import 'realm_class.dart'; - /// Creates a [Configuration] with schema objects for opening a [Realm]. - Configuration(List schemaObjects, - {String? path, - this.fifoFilesFallbackPath, - this.isReadOnly = false, - this.isInMemory = false, - this.schemaVersion = 0, - this.disableFormatUpgrade = false, - this.initialDataCallback, - this.shouldCompactCallback}) - : schema = RealmSchema(schemaObjects), - path = path ?? defaultPath; +abstract class Configuration { + /// The default filename of a [Realm] database + static const String defaultRealmName = "default.realm"; static String _initDefaultPath() { - var path = "default.realm"; + var path = defaultRealmName; if (Platform.isAndroid || Platform.isIOS) { path = _path.join(realmCore.getFilesPath(), path); } @@ -57,8 +38,7 @@ class Configuration { /// The platform dependent path to the default realm file - `default.realm`. /// /// If set it should contain the name of the realm file. Ex. /mypath/myrealm.realm - static String get defaultPath => _defaultPath ??= _initDefaultPath(); - static set defaultPath(String value) => _defaultPath = value; + static late String defaultPath = _initDefaultPath(); /// The platform dependent directory path used to store realm files /// @@ -70,6 +50,65 @@ class Configuration { return Directory.current.absolute.path; } + String? get fifoFilesFallbackPath; + String get path; + RealmSchema get schema; + int get schemaVersion; + List? get encryptionKey; + + @Deprecated('Use Configuration.local instead') + factory Configuration( + List schemaObjects, { + Function(Realm realm)? initialDataCallback, + int schemaVersion, + String? fifoFilesFallbackPath, + String? path, + bool disableFormatUpgrade, + bool isInMemory, + bool isReadOnly, + bool Function(int totalSize, int usedSize)? shouldCompactCallback, + }) = LocalConfiguration; + + factory Configuration.local( + List schemaObjects, { + Function(Realm realm)? initialDataCallback, + int schemaVersion, + String? fifoFilesFallbackPath, + String? path, + bool disableFormatUpgrade, + bool isReadOnly, + bool Function(int totalSize, int usedSize)? shouldCompactCallback, + }) = LocalConfiguration; + + factory Configuration.inMemory( + List schemaObjects, + String identifier, { + Function(Realm realm)? initialDataCallback, + int schemaVersion, + String? fifoFilesFallbackPath, + String? path, + }) = InMemoryConfiguration; + + factory Configuration.flexibleSync(List schemaObjects) = FlexibleSyncConfiguration; // TODO! +} + +/// Configuration used to create a [Realm] instance +/// {@category Configuration} +class _ConfigurationBase implements Configuration { + /// Creates a [Configuration] with schema objects for opening a [Realm]. + _ConfigurationBase( + List schemaObjects, { + String? path, + this.fifoFilesFallbackPath, + this.schemaVersion = 0, + this.encryptionKey, + }) : schema = RealmSchema(schemaObjects), + path = path ?? Configuration.defaultPath; + + /// The [RealmSchema] for this [Configuration] + @override + final RealmSchema schema; + /// The schema version used to open the [Realm] /// /// If omitted the default value of `0` is used to open the [Realm] @@ -77,13 +116,47 @@ class Configuration { /// Realm with a schema that contains objects that differ from their previous /// specification. If the schema was updated and the schemaVersion was not, /// an [RealmException] will be thrown. + @override final int schemaVersion; /// The path where the Realm should be stored. /// /// If omitted the [defaultPath] for the platform will be used. + @override final String path; + /// Specifies the FIFO special files fallback location. + /// Opening a [Realm] creates a number of FIFO special files in order to + /// coordinate access to the [Realm] across threads and processes. If the [Realm] file is stored in a location + /// that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the [Realm] cannot be opened. + /// In that case [Realm] needs a different location to store these files and this property defines that location. + /// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined + /// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files. + @override + final String? fifoFilesFallbackPath; + + @override + final List? encryptionKey; +} + +class LocalConfiguration extends _ConfigurationBase { + LocalConfiguration( + List schemaObjects, { + this.initialDataCallback, + int schemaVersion = 0, + String? fifoFilesFallbackPath, + String? path, + this.disableFormatUpgrade = false, + this.isInMemory = false, + this.isReadOnly = false, + this.shouldCompactCallback, + }) : super( + schemaObjects, + path: path, + fifoFilesFallbackPath: fifoFilesFallbackPath, + schemaVersion: schemaVersion, + ); + /// Specifies whether a [Realm] should be opened as read-only. /// This allows opening it from locked locations such as resources, /// bundled with an application. @@ -97,29 +170,13 @@ class Configuration { /// The file will also be used as swap space if the [Realm] becomes bigger than what fits in memory, /// but it is not persistent and will be removed when the last instance is closed. /// When all in-memory instance of [Realm] is closed all data in that [Realm] is deleted. - final bool isInMemory; - - /// Specifies the FIFO special files fallback location. - /// Opening a [Realm] creates a number of FIFO special files in order to - /// coordinate access to the [Realm] across threads and processes. If the [Realm] file is stored in a location - /// that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the [Realm] cannot be opened. - /// In that case [Realm] needs a different location to store these files and this property defines that location. - /// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined - /// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files. - final String? fifoFilesFallbackPath; + final bool isInMemory; // TODO: Get rid of this! /// Specifies if a [Realm] file format should be automatically upgraded /// if it was created with an older version of the [Realm] library. /// An exception will be thrown if a file format upgrade is required. final bool disableFormatUpgrade; - /// A function that will be executed only when the Realm is first created. - /// - /// The Realm instance passed in the callback already has a write transaction opened, so you can - /// add some initial data that your app needs. The function will not execute for existing - /// Realms, even if all objects in the Realm are deleted. - final Function(Realm realm)? initialDataCallback; - /// The function called when opening a Realm for the first time /// during the life of a process to determine if it should be compacted /// before being returned to the user. @@ -129,6 +186,39 @@ class Configuration { /// It returns true to indicate that an attempt to compact the file should be made. /// The compaction will be skipped if another process is currently accessing the realm file. final bool Function(int totalSize, int usedSize)? shouldCompactCallback; + + /// A function that will be executed only when the Realm is first created. + /// + /// The Realm instance passed in the callback already has a write transaction opened, so you can + /// add some initial data that your app needs. The function will not execute for existing + /// Realms, even if all objects in the Realm are deleted. + final Function(Realm realm)? initialDataCallback; +} + +class _SyncConfigurationBase extends _ConfigurationBase { + _SyncConfigurationBase(List schemaObjects) : super(schemaObjects); +} + +class FlexibleSyncConfiguration extends _SyncConfigurationBase { + FlexibleSyncConfiguration(List schemaObjects) : super(schemaObjects); +} + +class InMemoryConfiguration extends _ConfigurationBase { + InMemoryConfiguration( + List schemaObjects, + this.identifier, { + Function(Realm realm)? initialDataCallback, + int schemaVersion = 0, + String? fifoFilesFallbackPath, + String? path, + }) : super( + schemaObjects, + schemaVersion: schemaVersion, + fifoFilesFallbackPath: fifoFilesFallbackPath, + path: path, + ); + + final String identifier; } /// A collection of properties describing the underlying schema of a [RealmObject]. diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 3f4cb901c..43215e467 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -15,9 +15,6 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// - -// ignore_for_file: constant_identifier_names, non_constant_identifier_names - import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; @@ -28,8 +25,9 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import '../app.dart'; -import '../credentials.dart'; import '../collections.dart'; +import '../configuration.dart'; +import '../credentials.dart'; import '../init.dart'; import '../list.dart'; import '../realm_class.dart'; @@ -148,31 +146,36 @@ class _RealmCore { final schemaHandle = _createSchema(config.schema); final configPtr = _realmLib.realm_config_new(); final configHandle = ConfigHandle._(configPtr); + _realmLib.realm_config_set_schema(configHandle._pointer, schemaHandle._pointer); _realmLib.realm_config_set_schema_version(configHandle._pointer, config.schemaVersion); _realmLib.realm_config_set_path(configHandle._pointer, config.path.toUtf8Ptr(arena)); _realmLib.realm_config_set_scheduler(configHandle._pointer, schedulerHandle._pointer); - if (config.isReadOnly) { - _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); - } - if (config.isInMemory) { - _realmLib.realm_config_set_in_memory(configHandle._pointer, config.isInMemory); - } - if (config.fifoFilesFallbackPath != null) { - _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(arena)); - } - if (config.disableFormatUpgrade) { - _realmLib.realm_config_set_disable_format_upgrade(configHandle._pointer, config.disableFormatUpgrade); - } - if (config.initialDataCallback != null) { - _realmLib.realm_config_set_data_initialization_function( - configHandle._pointer, Pointer.fromFunction(initial_data_callback, FALSE), config.toWeakHandle()); - } - - if (config.shouldCompactCallback != null) { - _realmLib.realm_config_set_should_compact_on_launch_function( - configHandle._pointer, Pointer.fromFunction(should_compact_callback, 0), config.toWeakHandle()); + if (config is LocalConfiguration) { + if (config.initialDataCallback != null) { + _realmLib.realm_config_set_data_initialization_function( + configHandle._pointer, Pointer.fromFunction(initial_data_callback, FALSE), config.toWeakHandle()); + } + if (config.isReadOnly) { + _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); + } + if (config.isInMemory) { + // TODO: Get rid of this + _realmLib.realm_config_set_in_memory(configHandle._pointer, config.isInMemory); + } + if (config.fifoFilesFallbackPath != null) { + _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(arena)); + } + 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, 0), config.toWeakHandle()); + } + } else if (config is InMemoryConfiguration) { + _realmLib.realm_config_set_in_memory(configHandle._pointer, true); } return configHandle; @@ -181,7 +184,7 @@ class _RealmCore { static int initial_data_callback(Pointer userdata, Pointer realmHandle) { try { - final Configuration? config = userdata.toObject(); + final LocalConfiguration? config = userdata.toObject(); if (config == null) { return FALSE; } @@ -196,7 +199,7 @@ class _RealmCore { } static int should_compact_callback(Pointer userdata, int totalSize, int usedSize) { - final Configuration? config = userdata.toObject(); + final LocalConfiguration? config = userdata.toObject(); if (config == null) { return FALSE; } From 12284db81353e17ae6eeac43439ea1c77eddeb21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 22 Apr 2022 12:56:29 +0200 Subject: [PATCH 002/122] Deprecate the inMemory field. Replaced with InMemoryConfiguration --- lib/src/configuration.dart | 1 - test/configuration_test.dart | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 71f31ab12..402592e10 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -64,7 +64,6 @@ abstract class Configuration { String? fifoFilesFallbackPath, String? path, bool disableFormatUpgrade, - bool isInMemory, bool isReadOnly, bool Function(int totalSize, int usedSize)? shouldCompactCallback, }) = LocalConfiguration; diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 22ae0c8d0..b1a5be988 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -109,7 +109,7 @@ Future main([List? args]) async { }); test('Configuration inMemory - no files after closing realm', () { - Configuration config = Configuration([Car.schema], isInMemory: true); + Configuration config = Configuration.inMemory([Car.schema], ''); var realm = getRealm(config); realm.write(() => realm.add(Car('Tesla'))); realm.close(); @@ -117,7 +117,7 @@ Future main([List? args]) async { }); test('Configuration inMemory can not be readOnly', () { - Configuration config = Configuration([Car.schema], isInMemory: true); + Configuration config = Configuration.inMemory([Car.schema], ''); final realm = getRealm(config); expect(() { From a6f5988a0158ebaf6ca8141cb121246e229fe5c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 22 Apr 2022 15:08:54 +0200 Subject: [PATCH 003/122] Support for flexible sync configuration --- lib/src/configuration.dart | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 402592e10..02ac73436 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -88,7 +88,14 @@ abstract class Configuration { String? path, }) = InMemoryConfiguration; - factory Configuration.flexibleSync(List schemaObjects) = FlexibleSyncConfiguration; // TODO! + factory Configuration.flexibleSync( + User user, + List schemaObjects, { + Function(Realm realm)? initialDataCallback, + int schemaVersion, + String? fifoFilesFallbackPath, + String? path, + }) = FlexibleSyncConfiguration; } /// Configuration used to create a [Realm] instance @@ -195,11 +202,36 @@ class LocalConfiguration extends _ConfigurationBase { } class _SyncConfigurationBase extends _ConfigurationBase { - _SyncConfigurationBase(List schemaObjects) : super(schemaObjects); + final User user; + _SyncConfigurationBase( + this.user, + List schemaObjects, { + int schemaVersion = 0, + String? fifoFilesFallbackPath, + String? path, + }) : super( + schemaObjects, + schemaVersion: schemaVersion, + fifoFilesFallbackPath: fifoFilesFallbackPath, + path: path, + ); } class FlexibleSyncConfiguration extends _SyncConfigurationBase { - FlexibleSyncConfiguration(List schemaObjects) : super(schemaObjects); + FlexibleSyncConfiguration( + User user, + List schemaObjects, { + Function(Realm realm)? initialDataCallback, + int schemaVersion = 0, + String? fifoFilesFallbackPath, + String? path, + }) : super( + user, + schemaObjects, + schemaVersion: schemaVersion, + fifoFilesFallbackPath: fifoFilesFallbackPath, + path: path, + ); } class InMemoryConfiguration extends _ConfigurationBase { From 2cf1d321e03340c10cb72be5c63665366c1bc4f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 26 Apr 2022 00:16:38 +0200 Subject: [PATCH 004/122] Add SubscriptionSet (and MutableSubscriptionSet) --- lib/src/app.dart | 12 ++++ lib/src/native/realm_core.dart | 116 +++++++++++++++++++++++++++++--- lib/src/realm_class.dart | 18 ++++- lib/src/results.dart | 16 +++-- lib/src/subscription.dart | 118 +++++++++++++++++++++++++++++++++ test/subscription_test.dart | 70 +++++++++++++++++++ test/test.dart | 9 +++ test/test.g.dart | 29 ++++++++ 8 files changed, 369 insertions(+), 19 deletions(-) create mode 100644 lib/src/subscription.dart create mode 100644 test/subscription_test.dart diff --git a/lib/src/app.dart b/lib/src/app.dart index 12ba21eb5..2cefc215e 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -140,6 +140,18 @@ class App { return realmCore.getUsers(this).map((handle) => UserInternal.create(this, handle)); } + /// Removes the user's local credentials. This will also close any associated Sessions. + /// + /// If [user] is null logs out [currentUser] if it exists. + Future logout([User? user]) async { + user ??= currentUser; + if (user == null) { + return; + } + + return await realmCore.logOut(this, user); + } + /// 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); diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 43215e467..97587857e 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -33,8 +33,9 @@ import '../list.dart'; import '../realm_class.dart'; import '../realm_object.dart'; import '../results.dart'; -import 'realm_bindings.dart'; +import '../subscription.dart'; import '../user.dart'; +import 'realm_bindings.dart'; late RealmLibrary _realmLib; @@ -176,12 +177,93 @@ class _RealmCore { } } else if (config is InMemoryConfiguration) { _realmLib.realm_config_set_in_memory(configHandle._pointer, true); + } else if (config is FlexibleSyncConfiguration) { + final syncConfigPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_flx_sync_config_new(config.user.handle._pointer)); + try { + _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); + } finally { + _realmLib.realm_release(syncConfigPtr.cast()); + } } return configHandle; }); } + SubscriptionSetHandle getSubscriptions(Realm realm) { + return SubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_get_active_subscription_set(realm.handle._pointer))); + } + + 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); + } + + 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) { + return SubscriptionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_find_subscription_by_name( + subscriptions.handle._pointer, + name.toUtf8Ptr(arena), + ))); + }); + } + + SubscriptionHandle findSubscriptionByQuery(SubscriptionSet subscriptions, RealmResults query) { + return SubscriptionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_find_subscription_by_query( + subscriptions.handle._pointer, + query.queryHandle._pointer, + ))); + } + + MutableSubscriptionSetHandle makeSubscriptionSetMutable(SubscriptionSet subscriptions) { + return MutableSubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_make_subscription_set_mutable(subscriptions.handle._pointer))); + } + + SubscriptionSetHandle subscriptionSetCommit(MutableSubscriptionSet subscriptions){ + return SubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_subscription_set_commit(subscriptions.mutableHandle._pointer))); + } + + bool insertOrAssignSubscription(MutableSubscriptionSet subscriptions, RealmResults query, String? name) { + return using((arena) { + final out_index = arena(); + final out_inserted = arena(); + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign( + subscriptions.mutableHandle._pointer, + query.queryHandle._pointer, + name?.toUtf8Ptr(arena) ?? nullptr, + out_index, + out_inserted, + )); + return out_inserted.value > 0; + }); + } + + bool eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { + return using((arena) { + return _realmLib.realm_sync_subscription_set_erase_by_name( + subscriptions.mutableHandle._pointer, + name.toUtf8Ptr(arena), + ); + }); + } + + bool eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { + return _realmLib.realm_sync_subscription_set_erase_by_query( + subscriptions.mutableHandle._pointer, + query.queryHandle._pointer, + ); + } + static int initial_data_callback(Pointer userdata, Pointer realmHandle) { try { final LocalConfiguration? config = userdata.toObject(); @@ -357,7 +439,7 @@ class _RealmCore { return RealmResultsHandle._(pointer); } - RealmResultsHandle queryClass(Realm realm, int classKey, String query, List args) { + RealmQueryHandle queryClass(Realm realm, int classKey, String query, List args) { return using((arena) { final length = args.length; final argsPointer = arena(length); @@ -373,12 +455,11 @@ class _RealmCore { argsPointer, ), )); - final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_query_find_all(queryHandle._pointer)); - return RealmResultsHandle._(resultsPointer); + return queryHandle; }); } - RealmResultsHandle queryResults(RealmResults target, String query, List args) { + RealmQueryHandle queryResults(RealmResults target, String query, List args) { return using((arena) { final length = args.length; final argsPointer = arena(length); @@ -393,12 +474,16 @@ class _RealmCore { argsPointer, ), )); - final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_query_find_all(queryHandle._pointer)); - return RealmResultsHandle._(resultsPointer); + return queryHandle; }); } - RealmResultsHandle queryList(RealmList target, String query, List args) { + RealmResultsHandle queryFindAll(RealmQueryHandle queryHandle) { + final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_query_find_all(queryHandle._pointer)); + return RealmResultsHandle._(resultsPointer); + } + + RealmQueryHandle queryList(RealmList target, String query, List args) { return using((arena) { final length = args.length; final argsPointer = arena(length); @@ -413,8 +498,7 @@ class _RealmCore { argsPointer, ), )); - final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_query_find_all(queryHandle._pointer)); - return RealmResultsHandle._(resultsPointer); + return queryHandle; }); } @@ -1301,6 +1385,18 @@ class UserHandle extends Handle { UserHandle._(Pointer pointer) : super(pointer, 24); } +class SubscriptionHandle extends Handle { + SubscriptionHandle._(Pointer pointer) : super(pointer, 24); +} + +class SubscriptionSetHandle extends Handle { + SubscriptionSetHandle._(Pointer pointer) : super(pointer, 24); +} + +class MutableSubscriptionSetHandle extends Handle { + MutableSubscriptionSetHandle._(Pointer pointer) : super(pointer, 24); +} + extension on List { Pointer toInt8Ptr(Allocator allocator) { return toUint8Ptr(allocator).cast(); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 69a94aa38..96079661f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -28,6 +28,7 @@ import 'list.dart'; import 'native/realm_core.dart'; import 'realm_object.dart'; import 'results.dart'; +import 'subscription.dart'; // always expose with `show` to explicitly control the public API surface export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App; @@ -242,9 +243,13 @@ class Realm { /// /// The returned [RealmResults] allows iterating all the values without further filtering. RealmResults all() { + return query('TRUEPREDICATE'); + // TODO: The below is more efficient, but doesn't expose the query. We should fix the C-API. + /* RealmMetadata metadata = _getMetadata(T); final handle = realmCore.findAll(this, metadata.class_.key); return RealmResultsInternal.create(handle, this); + */ } /// Returns all [RealmObject]s that match the specified [query]. @@ -253,13 +258,22 @@ class Realm { /// 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 []]) { RealmMetadata metadata = _getMetadata(T); - final handle = realmCore.queryClass(this, metadata.class_.key, query, args); - return RealmResultsInternal.create(handle, this); + final queryHandle = realmCore.queryClass(this, metadata.class_.key, query, args); + return RealmResultsInternal.create(queryHandle, this); } /// Deletes all [RealmObject]s of type `T` in the `Realm` void deleteAll() => deleteMany(all()); + SubscriptionSet? _subscriptions; + SubscriptionSet? get subscriptions { + if (config is FlexibleSyncConfiguration) { + _subscriptions ??= SubscriptionSetInternal.create(realmCore.getSubscriptions(this)); + // TODO: Refresh _subscriptions, if needed. + } + return _subscriptions; + } + @override // ignore: hash_and_equals bool operator ==(Object other) { diff --git a/lib/src/results.dart b/lib/src/results.dart index cdca434fb..131aeb4b8 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -28,14 +28,15 @@ import 'realm_class.dart'; /// /// {@category Realm} class RealmResults extends collection.IterableBase { + final RealmQueryHandle _queryHandle; final RealmResultsHandle _handle; /// The Realm instance this collection belongs to. final Realm realm; - + final _supportsSnapshot = [] is List; - RealmResults._(this._handle, this.realm); + RealmResults._(this._queryHandle, this._handle, this.realm); /// Returns the element of type `T` at the specified [index]. T operator [](int index) { @@ -48,8 +49,8 @@ class RealmResults extends collection.IterableBase { /// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/) /// 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 handle = realmCore.queryResults(this, query, args); - return RealmResultsInternal.create(handle, realm); + final queryHandle = realmCore.queryResults(this, query, args); + return RealmResultsInternal.create(queryHandle, realm); } /// `true` if the `Results` collection is empty. @@ -62,7 +63,7 @@ class RealmResults extends collection.IterableBase { var results = this; if (_supportsSnapshot) { final handle = realmCore.resultsSnapshot(this); - results = RealmResultsInternal.create(handle, realm); + results = RealmResults._(_queryHandle, handle, realm); } return _RealmResultsIterator(results); } @@ -81,10 +82,11 @@ class RealmResults extends collection.IterableBase { /// @nodoc //RealmResults package internal members extension RealmResultsInternal on RealmResults { + RealmQueryHandle get queryHandle => _queryHandle; RealmResultsHandle get handle => _handle; - static RealmResults create(RealmResultsHandle handle, Realm realm) { - return RealmResults._(handle, realm); + static RealmResults create(RealmQueryHandle queryHandle, Realm realm) { + return RealmResults._(queryHandle, realmCore.queryFindAll(queryHandle), realm); } } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart new file mode 100644 index 000000000..baab3c3f0 --- /dev/null +++ b/lib/src/subscription.dart @@ -0,0 +1,118 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:async'; +import 'dart:collection'; + +import 'realm_class.dart'; + +import 'native/realm_core.dart'; + +class Subscription { + final SubscriptionHandle _handle; + + Subscription._(this._handle); +} + +class _SubscriptionIterator implements Iterator { + int _index = -1; + final SubscriptionSet _subscriptions; + + _SubscriptionIterator._(this._subscriptions); + + @override + Subscription get current => _subscriptions.elementAt(_index); + + @override + bool moveNext() => ++_index < _subscriptions.length; +} + +abstract class SubscriptionSet with IterableMixin { + SubscriptionSetHandle _handle; + + SubscriptionSet._(this._handle); + + Subscription? find(RealmResults query) { + return Subscription._(realmCore.findSubscriptionByQuery(this, query)); + } + + Subscription? findByName(String name) { + return Subscription._(realmCore.findSubscriptionByName(this, name)); + } + + @override + int get length => realmCore.getSubscriptionSetSize(this); + + @override + Subscription elementAt(int index) { + return Subscription._(realmCore.subscriptionAt(this, index)); + } + + @override + _SubscriptionIterator get iterator => _SubscriptionIterator._(this); + + void update(void Function(MutableSubscriptionSet subscriptions) action); +} + +extension SubscriptionSetInternal on SubscriptionSet { + SubscriptionSetHandle get handle => _handle; + + static SubscriptionSet create(SubscriptionSetHandle handle) => MutableSubscriptionSet._(handle); +} + +class MutableSubscriptionSet extends SubscriptionSet { + MutableSubscriptionSetHandle? _mutableHandle; + + MutableSubscriptionSet._(SubscriptionSetHandle handle) : super._(handle); + + @override + void update(void Function(MutableSubscriptionSet subscriptions) action) { + assert(_mutableHandle == null); + var commit = false; + try { + _mutableHandle = realmCore.makeSubscriptionSetMutable(this); + action(this); + commit = true; + } finally { + if (commit) { + _handle = realmCore.subscriptionSetCommit(this); + } + // _mutableHandle.release(); // TODO: Release early + _mutableHandle = null; + } + } + + bool addOrUpdate(RealmResults query, {String? name, bool update = true}) { + assert(_mutableHandle != null); + return realmCore.insertOrAssignSubscription(this, query, name); + } + + bool remove(RealmResults query) { + assert(_mutableHandle != null); + return realmCore.eraseSubscriptionByQuery(this, query); + } + + bool removeByName(String name) { + assert(_mutableHandle != null); + return realmCore.eraseSubscriptionByName(this, name); + } +} + +extension MutableSubscriptionSetInternal on MutableSubscriptionSet { + MutableSubscriptionSetHandle get mutableHandle => _mutableHandle!; +} diff --git a/test/subscription_test.dart b/test/subscription_test.dart new file mode 100644 index 000000000..0b0eb349c --- /dev/null +++ b/test/subscription_test.dart @@ -0,0 +1,70 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:io'; + +import 'package:test/expect.dart'; + +import '../lib/realm.dart'; +import 'test.dart'; + +Future main([List? args]) async { + print("Current PID $pid"); + + await setupTests(args); + + baasTest('Get subscriptions', (configuration) async { + final app = App(configuration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final realm = getRealm(Configuration.flexibleSync(user, [Task.schema])); + + expect(realm.subscriptions, isEmpty); + + final query = realm.all(); + + realm.subscriptions!.update((mutableSet) { + mutableSet.addOrUpdate(query); + }); + + expect(realm.subscriptions!.length, 1); + + realm.subscriptions!.update((mutableSet) { + mutableSet.remove(query); + }); + + expect(realm.subscriptions, isEmpty); + + final name = 'a random name'; + realm.subscriptions!.update((mutableSet) { + mutableSet.addOrUpdate(query, name: name); + }); + + expect(realm.subscriptions!.findByName(name), isNotNull); + + realm.subscriptions!.update((mutableSet) { + mutableSet.removeByName(name); + }); + + expect(realm.subscriptions, isEmpty); + // expect(realm.subscriptions!.findByName(name), isNull); + + realm.close(); + app.logout(user); + }); +} diff --git a/test/test.dart b/test/test.dart index f44ce8769..128c0f516 100644 --- a/test/test.dart +++ b/test/test.dart @@ -87,6 +87,13 @@ class $RemappedClass { late List<$RemappedClass> listProperty; } +@RealmModel() +class _Task { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; +} + String? testName; final baasApps = {}; final _openRealms = Queue(); @@ -239,6 +246,7 @@ Future baasTest( skip = skip || url == null ? "BAAS URL not present" : false; } + print('skip: $skip'); test(name, () async { final app = baasApps[appName.name] ?? baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); @@ -248,6 +256,7 @@ Future baasTest( baseUrl: url, baseFilePath: temporaryDir, ); + print('test'); return await testFunction(appConfig); }, skip: skip); } diff --git a/test/test.g.dart b/test/test.g.dart index 7cf6ddad3..1919a2aaa 100644 --- a/test/test.g.dart +++ b/test/test.g.dart @@ -327,3 +327,32 @@ class RemappedClass extends $RemappedClass with RealmEntity, RealmObject { ]); } } + +class Task extends _Task with RealmEntity, RealmObject { + Task( + ObjectId id, + ) { + RealmObject.set(this, '_id', id); + } + + Task._(); + + @override + ObjectId get id => RealmObject.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => throw RealmUnsupportedSetError(); + + @override + Stream> get changes => + RealmObject.getChanges(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(Task._); + return const SchemaObject(Task, 'Task', [ + SchemaProperty('_id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + ]); + } +} From 07f5f89c50ff79978efcfe21517620d1065880be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 26 Apr 2022 08:55:34 +0200 Subject: [PATCH 005/122] Add SubscriptionSet.waitForStateChangeSync. Only synchonous version for now, since async C-API doesn't allow userdata --- lib/src/native/realm_core.dart | 7 +++++++ lib/src/realm_class.dart | 8 +++++--- lib/src/subscription.dart | 14 +++++++++++++- test/subscription_test.dart | 2 ++ 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 97587857e..091e65021 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -225,6 +225,13 @@ class _RealmCore { ))); } + static void _stateChangeCallback(Pointer subscriptionSetPtr, int state) { + } + + void waitForSubscriptionSetStateChangeSync(SubscriptionSet subscriptions, SubscriptionSetState state) { + _realmLib.realm_sync_on_subscription_set_state_change_wait(subscriptions.handle._pointer, state.index); + } + MutableSubscriptionSetHandle makeSubscriptionSetMutable(SubscriptionSet subscriptions) { return MutableSubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_make_subscription_set_mutable(subscriptions.handle._pointer))); } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 96079661f..df538b1f8 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -30,8 +30,6 @@ import 'realm_object.dart'; import 'results.dart'; import 'subscription.dart'; -// always expose with `show` to explicitly control the public API surface -export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App; export 'package:realm_common/realm_common.dart' show Ignored, @@ -46,12 +44,16 @@ export 'package:realm_common/realm_common.dart' RealmPropertyType, ObjectId, Uuid; + +// always expose with `show` to explicitly control the public API surface +export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App; export "configuration.dart" show Configuration, RealmSchema, SchemaObject; +export 'credentials.dart' show Credentials, AuthProviderType, EmailPasswordAuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges; export 'realm_object.dart' show RealmEntity, RealmException, RealmObject, RealmObjectChanges; export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; -export 'credentials.dart' show Credentials, AuthProviderType, EmailPasswordAuthProvider; +export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState; /// A [Realm] instance represents a `Realm` database. diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index baab3c3f0..512d4fa3b 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -16,7 +16,6 @@ // //////////////////////////////////////////////////////////////////////////////// -import 'dart:async'; import 'dart:collection'; import 'realm_class.dart'; @@ -42,6 +41,15 @@ class _SubscriptionIterator implements Iterator { bool moveNext() => ++_index < _subscriptions.length; } +enum SubscriptionSetState { + uncommitted, + pending, + bootstrapping, + complete, + error, + superseded, +} + abstract class SubscriptionSet with IterableMixin { SubscriptionSetHandle _handle; @@ -55,6 +63,10 @@ abstract class SubscriptionSet with IterableMixin { return Subscription._(realmCore.findSubscriptionByName(this, name)); } + void waitForStateChange(SubscriptionSetState state) { + realmCore.waitForSubscriptionSetStateChangeSync(this, state); + } + @override int get length => realmCore.getSubscriptionSetSize(this); diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 0b0eb349c..5ca60bc00 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -64,6 +64,8 @@ Future main([List? args]) async { expect(realm.subscriptions, isEmpty); // expect(realm.subscriptions!.findByName(name), isNull); + realm.subscriptions!.waitForStateChange(SubscriptionSetState.complete); + realm.close(); app.logout(user); }); From 05351d368e2bce48919fa955cadae086c0877a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 26 Apr 2022 09:25:04 +0200 Subject: [PATCH 006/122] Align with kotlin naming --- lib/src/subscription.dart | 4 ++-- test/subscription_test.dart | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 512d4fa3b..9a671025e 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -78,7 +78,7 @@ abstract class SubscriptionSet with IterableMixin { @override _SubscriptionIterator get iterator => _SubscriptionIterator._(this); - void update(void Function(MutableSubscriptionSet subscriptions) action); + void update(void Function(MutableSubscriptionSet mutableSubscriptions) action); } extension SubscriptionSetInternal on SubscriptionSet { @@ -93,7 +93,7 @@ class MutableSubscriptionSet extends SubscriptionSet { MutableSubscriptionSet._(SubscriptionSetHandle handle) : super._(handle); @override - void update(void Function(MutableSubscriptionSet subscriptions) action) { + void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { assert(_mutableHandle == null); var commit = false; try { diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 5ca60bc00..98ff61634 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -44,21 +44,21 @@ Future main([List? args]) async { expect(realm.subscriptions!.length, 1); - realm.subscriptions!.update((mutableSet) { - mutableSet.remove(query); + realm.subscriptions!.update((mutableSubscriptions) { + mutableSubscriptions.remove(query); }); expect(realm.subscriptions, isEmpty); final name = 'a random name'; - realm.subscriptions!.update((mutableSet) { - mutableSet.addOrUpdate(query, name: name); + realm.subscriptions!.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(query, name: name); }); expect(realm.subscriptions!.findByName(name), isNotNull); - realm.subscriptions!.update((mutableSet) { - mutableSet.removeByName(name); + realm.subscriptions!.update((mutableSubscriptions) { + mutableSubscriptions.removeByName(name); }); expect(realm.subscriptions, isEmpty); From 49287a8fc5e1fff57145fc218648a5995b94ef3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 26 Apr 2022 11:08:01 +0200 Subject: [PATCH 007/122] Support session stop policy --- lib/src/configuration.dart | 13 +++++++++++++ lib/src/native/realm_core.dart | 1 + lib/src/realm_class.dart | 2 +- test/subscription_test.dart | 8 +++++--- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 02ac73436..285857df1 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -217,7 +217,15 @@ class _SyncConfigurationBase extends _ConfigurationBase { ); } +enum SessionStopPolicy { + immediately, // Immediately stop the session as soon as all Realms/Sessions go out of scope. + liveIndefinitely, // Never stop the session. + afterChangesUploaded, // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. +} + class FlexibleSyncConfiguration extends _SyncConfigurationBase { + SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; + FlexibleSyncConfiguration( User user, List schemaObjects, { @@ -234,6 +242,11 @@ class FlexibleSyncConfiguration extends _SyncConfigurationBase { ); } +extension FlexibleSyncConfigurationInternal on FlexibleSyncConfiguration { + SessionStopPolicy get sessionStopPolicy => _sessionStopPolicy; + set sessionStopPolicy(SessionStopPolicy value) => _sessionStopPolicy = value; +} + class InMemoryConfiguration extends _ConfigurationBase { InMemoryConfiguration( List schemaObjects, diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 091e65021..cd1e0de06 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -180,6 +180,7 @@ class _RealmCore { } else if (config is FlexibleSyncConfiguration) { 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_config_set_sync_config(configPtr, syncConfigPtr); } finally { _realmLib.realm_release(syncConfigPtr.cast()); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index df538b1f8..396878ef8 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -47,7 +47,7 @@ export 'package:realm_common/realm_common.dart' // always expose with `show` to explicitly control the public API surface export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App; -export "configuration.dart" show Configuration, RealmSchema, SchemaObject; +export "configuration.dart" show Configuration, RealmSchema, SchemaObject, FlexibleSyncConfiguration, LocalConfiguration, InMemoryConfiguration; export 'credentials.dart' show Credentials, AuthProviderType, EmailPasswordAuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges; export 'realm_object.dart' show RealmEntity, RealmException, RealmObject, RealmObjectChanges; diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 98ff61634..84d68ce6a 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -21,6 +21,7 @@ import 'dart:io'; import 'package:test/expect.dart'; import '../lib/realm.dart'; +import '../lib/src/configuration.dart'; import 'test.dart'; Future main([List? args]) async { @@ -28,11 +29,12 @@ Future main([List? args]) async { await setupTests(args); - baasTest('Get subscriptions', (configuration) async { - final app = App(configuration); + baasTest('Get subscriptions', (appConfiguration) async { + final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final realm = getRealm(Configuration.flexibleSync(user, [Task.schema])); + final configuration = (Configuration.flexibleSync(user, [Task.schema]) as FlexibleSyncConfiguration)..sessionStopPolicy = SessionStopPolicy.immediately; + final realm = getRealm(configuration); expect(realm.subscriptions, isEmpty); From e15e3c7b4ce1a7b4e5de5d2399192b9abcdd9fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 28 Apr 2022 11:38:08 +0200 Subject: [PATCH 008/122] Support async SubscriptionSet.waitForStateChange This requires support for out-of-isolate callbacks. To do this a dart specific EventLoopDispatcher and DispatchFreeUserdata is introduced. --- ffigen/config.yaml | 2 + ffigen/subscription_set.h | 1 + lib/src/app.dart | 12 --- lib/src/native/realm_bindings.dart | 86 +++++++++++++++--- lib/src/native/realm_core.dart | 27 +++++- lib/src/realm_class.dart | 2 +- lib/src/subscription.dart | 12 +-- src/CMakeLists.txt | 10 ++- src/event_loop_dispatcher.hpp | 134 +++++++++++++++++++++++++++++ src/realm_dart.h | 1 - src/subscription_set.cpp | 57 ++++++++++++ src/subscription_set.h | 45 ++++++++++ test/subscription_test.dart | 3 +- test/test.dart | 2 - 14 files changed, 353 insertions(+), 41 deletions(-) create mode 120000 ffigen/subscription_set.h create mode 100644 src/event_loop_dispatcher.hpp create mode 100644 src/subscription_set.cpp create mode 100644 src/subscription_set.h diff --git a/ffigen/config.yaml b/ffigen/config.yaml index ebdf2300d..22243f7d8 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -7,11 +7,13 @@ headers: - 'realm_dart.h' - 'realm_dart_scheduler.h' - 'realm_android_platform.h' + - 'subscription_set.h' include-directives: #generate only for these headers - 'realm.h' - 'realm_dart.h' - 'realm_dart_scheduler.h' - 'realm_android_platform.h' + - 'subscription_set.h' compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' diff --git a/ffigen/subscription_set.h b/ffigen/subscription_set.h new file mode 120000 index 000000000..49920850e --- /dev/null +++ b/ffigen/subscription_set.h @@ -0,0 +1 @@ +../src/subscription_set.h \ No newline at end of file diff --git a/lib/src/app.dart b/lib/src/app.dart index 2cefc215e..12ba21eb5 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -140,18 +140,6 @@ class App { return realmCore.getUsers(this).map((handle) => UserInternal.create(this, handle)); } - /// Removes the user's local credentials. This will also close any associated Sessions. - /// - /// If [user] is null logs out [currentUser] if it exists. - Future logout([User? user]) async { - user ??= currentUser; - if (user == null) { - return; - } - - return await realmCore.logOut(this, user); - } - /// 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); diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index b37a37378..aac7aad97 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -7216,7 +7216,7 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer)>(); - /// Wait uptill subscripton set state is equal to the state passed as parameter. + /// Wait until subscripton set state is equal to the state passed as parameter. /// This is a blocking operation. /// @return the current subscription state int realm_sync_on_subscription_set_state_change_wait( @@ -7241,27 +7241,39 @@ class RealmLibrary { /// This is an asynchronous operation. /// @return true/false if the handler was registered correctly bool realm_sync_on_subscription_set_state_change_async( - ffi.Pointer arg0, - int arg1, - realm_sync_on_subscription_state_changed arg2, + ffi.Pointer subscription_set, + int notify_when, + realm_sync_on_subscription_state_changed callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, ) { return _realm_sync_on_subscription_set_state_change_async( - arg0, - arg1, - arg2, + subscription_set, + notify_when, + callback, + userdata, + userdata_free, ) != 0; } late final _realm_sync_on_subscription_set_state_change_asyncPtr = _lookup< ffi.NativeFunction< - ffi.Uint8 Function(ffi.Pointer, - ffi.Int32, realm_sync_on_subscription_state_changed)>>( + ffi.Uint8 Function( + ffi.Pointer, + ffi.Int32, + realm_sync_on_subscription_state_changed, + ffi.Pointer, + realm_free_userdata_func_t)>>( 'realm_sync_on_subscription_set_state_change_async'); late final _realm_sync_on_subscription_set_state_change_async = _realm_sync_on_subscription_set_state_change_asyncPtr.asFunction< - int Function(ffi.Pointer, int, - realm_sync_on_subscription_state_changed)>(); + int Function( + ffi.Pointer, + int, + realm_sync_on_subscription_state_changed, + ffi.Pointer, + realm_free_userdata_func_t)>(); /// Retrieve version for the subscription set passed as parameter /// @return subscription set version if the poiter to the subscription is valid @@ -8190,6 +8202,54 @@ class RealmLibrary { 'realm_dart_get_files_path'); late final _realm_dart_get_files_path = _realm_dart_get_files_pathPtr .asFunction Function()>(); + + /// Register a handler in order to be notified when subscription set is equal to the one passed as parameter + /// This is an asynchronous operation. + /// + /// @return true/false if the handler was registered correctly + /// + /// This is dart specific version of realm_dart_on_subscription_set_state_change_async. + /// Unlike the original method, this one uses event_loop_dispatcher to ensure the callback + /// is handled on the correct isolate thread. + bool realm_dart_sync_on_subscription_set_state_change_async( + ffi.Pointer subscription_set, + int notify_when, + realm_sync_on_subscription_state_changed callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_on_subscription_set_state_change_async( + subscription_set, + notify_when, + callback, + userdata, + userdata_free, + scheduler, + ) != + 0; + } + + late final _realm_dart_sync_on_subscription_set_state_change_asyncPtr = + _lookup< + ffi.NativeFunction< + ffi.Uint8 Function( + ffi.Pointer, + ffi.Int32, + realm_sync_on_subscription_state_changed, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_on_subscription_set_state_change_async'); + late final _realm_dart_sync_on_subscription_set_state_change_async = + _realm_dart_sync_on_subscription_set_state_change_asyncPtr.asFunction< + int Function( + ffi.Pointer, + int, + realm_sync_on_subscription_state_changed, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); } class shared_realm extends ffi.Opaque {} @@ -9113,9 +9173,7 @@ typedef realm_sync_ssl_verify_func_t = ffi.Pointer< ffi.Int32)>>; typedef realm_flx_sync_subscription_set_t = realm_flx_sync_subscription_set; typedef realm_sync_on_subscription_state_changed = ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, ffi.Int32)>>; + ffi.NativeFunction, ffi.Int32)>>; typedef realm_flx_sync_subscription_t = realm_flx_sync_subscription; typedef realm_flx_sync_mutable_subscription_set_t = realm_flx_sync_mutable_subscription_set; diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index cd1e0de06..3c1145647 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -226,18 +226,39 @@ class _RealmCore { ))); } - static void _stateChangeCallback(Pointer subscriptionSetPtr, int state) { + static void _stateChangeCallback(Pointer userdata, int state) { + final completer = userdata.toObject>(isPersistent: true); + if (completer == null) { + return; + } + + // TODO: What about errors?! + + completer.complete(state); } void waitForSubscriptionSetStateChangeSync(SubscriptionSet subscriptions, SubscriptionSetState state) { _realmLib.realm_sync_on_subscription_set_state_change_wait(subscriptions.handle._pointer, state.index); } + Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen) { + final completer = Completer(); + _realmLib.realm_dart_sync_on_subscription_set_state_change_async( + subscriptions.handle._pointer, + notifyWhen.index, + Pointer.fromFunction(_stateChangeCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + subscriptions.realm.scheduler.handle._pointer, + ); + return completer.future; + } + MutableSubscriptionSetHandle makeSubscriptionSetMutable(SubscriptionSet subscriptions) { return MutableSubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_make_subscription_set_mutable(subscriptions.handle._pointer))); } - SubscriptionSetHandle subscriptionSetCommit(MutableSubscriptionSet subscriptions){ + SubscriptionSetHandle subscriptionSetCommit(MutableSubscriptionSet subscriptions) { return SubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_subscription_set_commit(subscriptions.mutableHandle._pointer))); } @@ -940,8 +961,10 @@ class _RealmCore { }); } + final doNotDie = Set(); AppHandle getApp(AppConfiguration configuration) { final httpTransportHandle = _createHttpTransport(configuration.httpClient); + doNotDie.add(httpTransportHandle); final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); final syncClientConfigHandle = _createSyncClientConfig(configuration); final realmAppPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_get(appConfigHandle._pointer, syncClientConfigHandle._pointer)); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 396878ef8..41fbae19f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -270,7 +270,7 @@ class Realm { SubscriptionSet? _subscriptions; SubscriptionSet? get subscriptions { if (config is FlexibleSyncConfiguration) { - _subscriptions ??= SubscriptionSetInternal.create(realmCore.getSubscriptions(this)); + _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); // TODO: Refresh _subscriptions, if needed. } return _subscriptions; diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 9a671025e..617975646 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -51,9 +51,10 @@ enum SubscriptionSetState { } abstract class SubscriptionSet with IterableMixin { + Realm _realm; SubscriptionSetHandle _handle; - SubscriptionSet._(this._handle); + SubscriptionSet._(this._realm, this._handle); Subscription? find(RealmResults query) { return Subscription._(realmCore.findSubscriptionByQuery(this, query)); @@ -63,8 +64,8 @@ abstract class SubscriptionSet with IterableMixin { return Subscription._(realmCore.findSubscriptionByName(this, name)); } - void waitForStateChange(SubscriptionSetState state) { - realmCore.waitForSubscriptionSetStateChangeSync(this, state); + Future waitForStateChange(SubscriptionSetState state) async { + return SubscriptionSetState.values[await realmCore.waitForSubscriptionSetStateChange(this, state)]; } @override @@ -82,15 +83,16 @@ abstract class SubscriptionSet with IterableMixin { } extension SubscriptionSetInternal on SubscriptionSet { + Realm get realm => _realm; SubscriptionSetHandle get handle => _handle; - static SubscriptionSet create(SubscriptionSetHandle handle) => MutableSubscriptionSet._(handle); + static SubscriptionSet create(Realm realm, SubscriptionSetHandle handle) => MutableSubscriptionSet._(realm, handle); } class MutableSubscriptionSet extends SubscriptionSet { MutableSubscriptionSetHandle? _mutableHandle; - MutableSubscriptionSet._(SubscriptionSetHandle handle) : super._(handle); + MutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle) : super._(realm, handle); @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index abe916c1c..48cd8def5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,14 +8,16 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp ) set(HEADERS realm_dart.h realm_dart_scheduler.h realm-core/src/realm.h + subscription_set.h ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) @@ -33,6 +35,10 @@ else() target_link_options(realm_dart PRIVATE LINKER:--whole-archive $ LINKER:--no-whole-archive) endif() +if(REALM_ENABLE_SYNC) + target_compile_definitions(realm_dart PUBLIC REALM_ENABLE_SYNC=1) +endif() + string(APPEND OUTPUT_DIR "${PROJECT_SOURCE_DIR}/binary") if(CMAKE_SYSTEM_NAME STREQUAL "Windows") string(APPEND OUTPUT_DIR "/windows") diff --git a/src/event_loop_dispatcher.hpp b/src/event_loop_dispatcher.hpp new file mode 100644 index 000000000..537f61277 --- /dev/null +++ b/src/event_loop_dispatcher.hpp @@ -0,0 +1,134 @@ +//////////////////////////////////////////////////////////////////////////// +// +// Copyright 2019 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_EVENT_LOOP_DISPATCHER_HPP +#define REALM_DART_EVENT_LOOP_DISPATCHER_HPP + +#include + +#include +#include +#include "dart_api_dl.h" + +namespace realm::util { + +template +class EventLoopDispatcher; + +template +class EventLoopDispatcher { + using Tuple = std::tuple::type...>; + +private: + const std::shared_ptr> m_func; + std::shared_ptr m_scheduler; // = util::Scheduler::make_default(); + +public: + EventLoopDispatcher(std::shared_ptr scheduler, util::UniqueFunction func) + : m_func(std::make_shared>(std::move(func))), m_scheduler(scheduler) + { + } + + const util::UniqueFunction& func() const + { + return *m_func; + } + + void operator()(Args... args) + { + m_scheduler->invoke( + [scheduler = m_scheduler, func = m_func, args = std::make_tuple(std::forward(args)...)]() mutable { + std::apply(*func, std::move(args)); + // Each invocation block will retain the scheduler, so the scheduler + // will not be released until all blocks are called + }); + } +}; + +namespace _impl::ForEventLoopDispatcher { +template +struct ExtractSignatureImpl { +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +template +struct ExtractSignatureImpl { + using signature = void(Args...); +}; +// Note: no && specializations since util::UniqueFunction doesn't support them, so you can't construct an +// EventLoopDispatcher from something with that anyway. + +template +using ExtractSignature = typename ExtractSignatureImpl::signature; +} // namespace _impl::ForEventLoopDispatcher + +// Deduction guide for function pointers. +template +EventLoopDispatcher(std::shared_ptr, void (*)(Args...)) -> EventLoopDispatcher; + +// Deduction guide for callable objects, such as lambdas. Only supports types with a non-overloaded, non-templated +// call operator, so no polymorphic (auto argument) lambdas. +template > +EventLoopDispatcher(std::shared_ptr, const T&) -> EventLoopDispatcher; + +struct DispatchFreeUserdata { + const std::shared_ptr m_scheduler; + realm_free_userdata_func_t m_func; + DispatchFreeUserdata(std::shared_ptr scheduler, realm_free_userdata_func_t func = nullptr) + : m_scheduler(scheduler), m_func(func) + { + } + void operator()(void* ptr) + { + if (m_func) { + m_scheduler->invoke([func = m_func, ptr]() { + func(ptr); + }); + } + } +}; + +} // namespace realm::util + +#endif diff --git a/src/realm_dart.h b/src/realm_dart.h index dc4462669..26acb9702 100644 --- a/src/realm_dart.h +++ b/src/realm_dart.h @@ -35,5 +35,4 @@ RLM_API void* object_to_persistent_handle(Dart_Handle handle); RLM_API Dart_Handle persistent_handle_to_object(void* handle); RLM_API void delete_persistent_handle(void* handle); - #endif // REALM_DART_H \ No newline at end of file diff --git a/src/subscription_set.cpp b/src/subscription_set.cpp new file mode 100644 index 000000000..c63792256 --- /dev/null +++ b/src/subscription_set.cpp @@ -0,0 +1,57 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include "subscription_set.h" + +#include + +#include +#include +#include + +#include "event_loop_dispatcher.hpp" + +namespace realm::c_api { + +using namespace realm::sync; + +RLM_API bool realm_dart_sync_on_subscription_set_state_change_async( + const realm_flx_sync_subscription_set_t* subscription_set, + realm_flx_sync_subscription_set_state_e notify_when, + realm_dart_sync_on_subscription_state_changed callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + return wrap_err([&]() { + auto future_state = subscription_set->get_state_change_notification(SubscriptionSet::State{notify_when}); + std::move(future_state) + .get_async([callback, scheduler, userdata = SharedUserdata(userdata, util::DispatchFreeUserdata(*scheduler, userdata_free))](const StatusWith& state) -> void { + auto cb = util::EventLoopDispatcher{*scheduler, callback}; + if (state.is_ok()) { + cb(userdata.get(), realm_flx_sync_subscription_set_state_e(static_cast(state.get_value()))); + } + else { + cb(userdata.get(), realm_flx_sync_subscription_set_state_e::RLM_SYNC_SUBSCRIPTION_ERROR); + } + }); + return true; + }); +} + +} // namespace realm::c_api diff --git a/src/subscription_set.h b/src/subscription_set.h new file mode 100644 index 000000000..003f0d008 --- /dev/null +++ b/src/subscription_set.h @@ -0,0 +1,45 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_SUBSCRIPTION_SET_H +#define REALM_DART_SUBSCRIPTION_SET_H + +#include "realm.h" + +typedef void (*realm_dart_sync_on_subscription_state_changed)(void* userdata, + realm_flx_sync_subscription_set_state_e state); + +/** + * Register a handler in order to be notified when subscription set is equal to the one passed as parameter + * This is an asynchronous operation. + * + * @return true/false if the handler was registered correctly + * + * This is dart specific version of realm_dart_on_subscription_set_state_change_async. + * Unlike the original method, this one uses event_loop_dispatcher to ensure the callback + * is handled on the correct isolate thread. + */ +RLM_API bool +realm_dart_sync_on_subscription_set_state_change_async(const realm_flx_sync_subscription_set_t* subscription_set, + realm_flx_sync_subscription_set_state_e notify_when, + realm_dart_sync_on_subscription_state_changed callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +#endif // REALM_DART_SUBSCRIPTION_SET_H \ No newline at end of file diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 84d68ce6a..f8ee728ed 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -66,9 +66,8 @@ Future main([List? args]) async { expect(realm.subscriptions, isEmpty); // expect(realm.subscriptions!.findByName(name), isNull); - realm.subscriptions!.waitForStateChange(SubscriptionSetState.complete); + await realm.subscriptions!.waitForStateChange(SubscriptionSetState.complete); realm.close(); - app.logout(user); }); } diff --git a/test/test.dart b/test/test.dart index 128c0f516..cd5a91265 100644 --- a/test/test.dart +++ b/test/test.dart @@ -246,7 +246,6 @@ Future baasTest( skip = skip || url == null ? "BAAS URL not present" : false; } - print('skip: $skip'); test(name, () async { final app = baasApps[appName.name] ?? baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); @@ -256,7 +255,6 @@ Future baasTest( baseUrl: url, baseFilePath: temporaryDir, ); - print('test'); return await testFunction(appConfig); }, skip: skip); } From 80530ce1080c8157a336ea6e903e4f1b97a49034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 2 May 2022 10:07:56 +0200 Subject: [PATCH 009/122] Make subscription non-nullable. Throw, if configuration not a FlexibleSyncConfiguration --- lib/src/realm_class.dart | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 41fbae19f..7007db102 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -268,12 +268,11 @@ class Realm { void deleteAll() => deleteMany(all()); SubscriptionSet? _subscriptions; - SubscriptionSet? get subscriptions { - if (config is FlexibleSyncConfiguration) { - _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); - // TODO: Refresh _subscriptions, if needed. - } - return _subscriptions; + /// The active [subscriptions] for this [Realm] + SubscriptionSet get subscriptions { + if (config is! FlexibleSyncConfiguration) throw RealmError('Does not support flexible synchronization'); + _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); + return _subscriptions!; } @override From 50d1edd9bcb43de8e753d2813fb9fd01c153bba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 2 May 2022 10:41:06 +0200 Subject: [PATCH 010/122] Add version & state properties to SubscriptionSet Also, SubscriptionSet.remove* are now void methods, and corresponding _RealmCore functions uses invokeGetBool. --- lib/src/native/realm_bindings.dart | 46 +++++++---------- lib/src/native/realm_core.dart | 38 ++++++++++---- lib/src/subscription.dart | 13 ++++- test/subscription_test.dart | 81 ++++++++++++++++++++++-------- 4 files changed, 118 insertions(+), 60 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index aac7aad97..3e64097dc 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -7216,7 +7216,7 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer)>(); - /// Wait until subscripton set state is equal to the state passed as parameter. + /// Wait uptill subscripton set state is equal to the state passed as parameter. /// This is a blocking operation. /// @return the current subscription state int realm_sync_on_subscription_set_state_change_wait( @@ -7241,39 +7241,27 @@ class RealmLibrary { /// This is an asynchronous operation. /// @return true/false if the handler was registered correctly bool realm_sync_on_subscription_set_state_change_async( - ffi.Pointer subscription_set, - int notify_when, - realm_sync_on_subscription_state_changed callback, - ffi.Pointer userdata, - realm_free_userdata_func_t userdata_free, + ffi.Pointer arg0, + int arg1, + realm_sync_on_subscription_state_changed arg2, ) { return _realm_sync_on_subscription_set_state_change_async( - subscription_set, - notify_when, - callback, - userdata, - userdata_free, + arg0, + arg1, + arg2, ) != 0; } late final _realm_sync_on_subscription_set_state_change_asyncPtr = _lookup< ffi.NativeFunction< - ffi.Uint8 Function( - ffi.Pointer, - ffi.Int32, - realm_sync_on_subscription_state_changed, - ffi.Pointer, - realm_free_userdata_func_t)>>( + ffi.Uint8 Function(ffi.Pointer, + ffi.Int32, realm_sync_on_subscription_state_changed)>>( 'realm_sync_on_subscription_set_state_change_async'); late final _realm_sync_on_subscription_set_state_change_async = _realm_sync_on_subscription_set_state_change_asyncPtr.asFunction< - int Function( - ffi.Pointer, - int, - realm_sync_on_subscription_state_changed, - ffi.Pointer, - realm_free_userdata_func_t)>(); + int Function(ffi.Pointer, int, + realm_sync_on_subscription_state_changed)>(); /// Retrieve version for the subscription set passed as parameter /// @return subscription set version if the poiter to the subscription is valid @@ -8214,7 +8202,7 @@ class RealmLibrary { bool realm_dart_sync_on_subscription_set_state_change_async( ffi.Pointer subscription_set, int notify_when, - realm_sync_on_subscription_state_changed callback, + realm_dart_sync_on_subscription_state_changed callback, ffi.Pointer userdata, realm_free_userdata_func_t userdata_free, ffi.Pointer scheduler, @@ -8236,7 +8224,7 @@ class RealmLibrary { ffi.Uint8 Function( ffi.Pointer, ffi.Int32, - realm_sync_on_subscription_state_changed, + realm_dart_sync_on_subscription_state_changed, ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>>( @@ -8246,7 +8234,7 @@ class RealmLibrary { int Function( ffi.Pointer, int, - realm_sync_on_subscription_state_changed, + realm_dart_sync_on_subscription_state_changed, ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>(); @@ -9173,7 +9161,9 @@ typedef realm_sync_ssl_verify_func_t = ffi.Pointer< ffi.Int32)>>; typedef realm_flx_sync_subscription_set_t = realm_flx_sync_subscription_set; typedef realm_sync_on_subscription_state_changed = ffi.Pointer< - ffi.NativeFunction, ffi.Int32)>>; + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, ffi.Int32)>>; typedef realm_flx_sync_subscription_t = realm_flx_sync_subscription; typedef realm_flx_sync_mutable_subscription_set_t = realm_flx_sync_mutable_subscription_set; @@ -9213,3 +9203,5 @@ class _Dart_FinalizableHandle extends ffi.Opaque {} /// A port is used to send or receive inter-isolate messages typedef Dart_Port = ffi.Int64; +typedef realm_dart_sync_on_subscription_state_changed = ffi.Pointer< + ffi.NativeFunction, ffi.Int32)>>; diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 3c1145647..464ac1b14 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -254,6 +254,14 @@ class _RealmCore { return completer.future; } + int subscriptionSetVersion(SubscriptionSet subscriptions) { + return _realmLib.realm_sync_subscription_set_version(subscriptions.handle._pointer); + } + + int subscriptionSetState(SubscriptionSet subscriptions) { + return _realmLib.realm_sync_subscription_set_state(subscriptions.handle._pointer); + } + MutableSubscriptionSetHandle makeSubscriptionSetMutable(SubscriptionSet subscriptions) { return MutableSubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_make_subscription_set_mutable(subscriptions.handle._pointer))); } @@ -277,20 +285,28 @@ class _RealmCore { }); } - bool eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { - return using((arena) { - return _realmLib.realm_sync_subscription_set_erase_by_name( - subscriptions.mutableHandle._pointer, - name.toUtf8Ptr(arena), - ); + void eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { + using((arena) { + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_name( + subscriptions.mutableHandle._pointer, + name.toUtf8Ptr(arena), + )); }); } - bool eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { - return _realmLib.realm_sync_subscription_set_erase_by_query( - subscriptions.mutableHandle._pointer, - query.queryHandle._pointer, - ); + void eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_query( + subscriptions.mutableHandle._pointer, + query.queryHandle._pointer, + )); + } + + void clearSubscriptionSet(MutableSubscriptionSet subscriptions) { + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_clear(subscriptions.mutableHandle._pointer)); + } + + void refreshSubscriptionSet(MutableSubscriptionSet subscriptions) { + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_refresh(subscriptions.handle._pointer)); } static int initial_data_callback(Pointer userdata, Pointer realmHandle) { diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 617975646..b19136b1e 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -80,6 +80,10 @@ abstract class SubscriptionSet with IterableMixin { _SubscriptionIterator get iterator => _SubscriptionIterator._(this); void update(void Function(MutableSubscriptionSet mutableSubscriptions) action); + + int get version => realmCore.subscriptionSetVersion(this); + + SubscriptionSetState get state => SubscriptionSetState.values[realmCore.subscriptionSetState(this)]; } extension SubscriptionSetInternal on SubscriptionSet { @@ -116,15 +120,20 @@ class MutableSubscriptionSet extends SubscriptionSet { return realmCore.insertOrAssignSubscription(this, query, name); } - bool remove(RealmResults query) { + void remove(RealmResults query) { assert(_mutableHandle != null); return realmCore.eraseSubscriptionByQuery(this, query); } - bool removeByName(String name) { + void removeByName(String name) { assert(_mutableHandle != null); return realmCore.eraseSubscriptionByName(this, name); } + + void removeAll() { + assert(_mutableHandle != null); + return realmCore.clearSubscriptionSet(this); + } } extension MutableSubscriptionSetInternal on MutableSubscriptionSet { diff --git a/test/subscription_test.dart b/test/subscription_test.dart index f8ee728ed..59b703338 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -16,58 +16,99 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:async'; import 'dart:io'; +import 'package:meta/meta.dart'; import 'package:test/expect.dart'; import '../lib/realm.dart'; import '../lib/src/configuration.dart'; import 'test.dart'; -Future main([List? args]) async { - print("Current PID $pid"); - - await setupTests(args); - - baasTest('Get subscriptions', (appConfiguration) async { +@isTest +void testSubscriptions(String name, FutureOr Function(Realm) tester) async { + baasTest(name, (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = (Configuration.flexibleSync(user, [Task.schema]) as FlexibleSyncConfiguration)..sessionStopPolicy = SessionStopPolicy.immediately; final realm = getRealm(configuration); + try { + await tester(realm); + } finally { + realm.close(); + } + }); +} + +Future main([List? args]) async { + print("Current PID $pid"); + + await setupTests(args); + + test('Get subscriptions throws on wrong configuration', () { + final config = Configuration.local([Task.schema]); + final realm = getRealm(config); + expect(() => realm.subscriptions, throws()); + }); + + testSubscriptions('SubscriptionSet.state', (realm) { + expect(realm.subscriptions.state, SubscriptionSetState.uncommitted); + }); - expect(realm.subscriptions, isEmpty); + testSubscriptions('SubscriptionSet.version', (realm) async { + final subscriptions = realm.subscriptions; + expect(subscriptions.version, 0); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.all()); + }); + + expect(subscriptions.length, 1); + expect(subscriptions.version, 1); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.removeAll(); + }); + + expect(subscriptions.length, 0); + expect(subscriptions.version, 2); + }); + + testSubscriptions('Get subscriptions', (realm) async { + final subscriptions = realm.subscriptions; + + expect(subscriptions, isEmpty); final query = realm.all(); - realm.subscriptions!.update((mutableSet) { - mutableSet.addOrUpdate(query); + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(query); }); - expect(realm.subscriptions!.length, 1); + expect(subscriptions.length, 1); - realm.subscriptions!.update((mutableSubscriptions) { + subscriptions.update((mutableSubscriptions) { mutableSubscriptions.remove(query); }); - expect(realm.subscriptions, isEmpty); + expect(subscriptions, isEmpty); final name = 'a random name'; - realm.subscriptions!.update((mutableSubscriptions) { + subscriptions.update((mutableSubscriptions) { mutableSubscriptions.addOrUpdate(query, name: name); }); - expect(realm.subscriptions!.findByName(name), isNotNull); + expect(subscriptions.findByName(name), isNotNull); - realm.subscriptions!.update((mutableSubscriptions) { + subscriptions.update((mutableSubscriptions) { mutableSubscriptions.removeByName(name); }); - expect(realm.subscriptions, isEmpty); - // expect(realm.subscriptions!.findByName(name), isNull); - - await realm.subscriptions!.waitForStateChange(SubscriptionSetState.complete); + expect(subscriptions, isEmpty); + // expect(realm.subscriptions!.findByName(name), isNull); // TODO - realm.close(); + await subscriptions.waitForStateChange(SubscriptionSetState.complete); }); } From 28fdbdd030b6cbc4c7c0d86f0050d9489defe89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 2 May 2022 11:05:04 +0200 Subject: [PATCH 011/122] Annoying hack --- test/test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.dart b/test/test.dart index cd5a91265..577dfe3ad 100644 --- a/test/test.dart +++ b/test/test.dart @@ -155,7 +155,7 @@ Future setupTests(List? args) async { try { Realm.deleteRealm(path); } catch (e) { - fail("Can not delete realm at path: $path. Did you forget to close it?"); + print("Can not delete realm at path: $path. Did you forget to close it?"); } String pathKey = _path.basenameWithoutExtension(path); String realmDir = _path.dirname(path); From d97fe8eccbc55cca090427cdca12e4f874d8ea77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 2 May 2022 12:08:29 +0200 Subject: [PATCH 012/122] Fix SubscriptionSet.find* when nothing found When nothing is found we should return null on find. Hence we cannot use invokeGetPointer, and also we need to pass null up the conversion stack. --- lib/src/native/realm_core.dart | 26 ++++++++--- lib/src/subscription.dart | 10 +++-- lib/src/util.dart | 25 +++++++++++ test/subscription_test.dart | 82 +++++++++++++++++++++++++++++++++- 4 files changed, 130 insertions(+), 13 deletions(-) create mode 100644 lib/src/util.dart diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 464ac1b14..399f5e061 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -210,20 +210,24 @@ class _RealmCore { ))); } - SubscriptionHandle findSubscriptionByName(SubscriptionSet subscriptions, String name) { + SubscriptionHandle? findSubscriptionByName(SubscriptionSet subscriptions, String name) { return using((arena) { - return SubscriptionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_find_subscription_by_name( + return _realmLib + .realm_sync_find_subscription_by_name( subscriptions.handle._pointer, name.toUtf8Ptr(arena), - ))); + ) + .convert(SubscriptionHandle._); }); } - SubscriptionHandle findSubscriptionByQuery(SubscriptionSet subscriptions, RealmResults query) { - return SubscriptionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_find_subscription_by_query( + SubscriptionHandle? findSubscriptionByQuery(SubscriptionSet subscriptions, RealmResults query) { + return _realmLib + .realm_sync_find_subscription_by_query( subscriptions.handle._pointer, query.queryHandle._pointer, - ))); + ) + .convert(SubscriptionHandle._); } static void _stateChangeCallback(Pointer userdata, int state) { @@ -259,6 +263,7 @@ class _RealmCore { } int subscriptionSetState(SubscriptionSet subscriptions) { + refreshSubscriptionSet(subscriptions); return _realmLib.realm_sync_subscription_set_state(subscriptions.handle._pointer); } @@ -305,7 +310,7 @@ class _RealmCore { _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_clear(subscriptions.mutableHandle._pointer)); } - void refreshSubscriptionSet(MutableSubscriptionSet subscriptions) { + void refreshSubscriptionSet(SubscriptionSet subscriptions) { _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_refresh(subscriptions.handle._pointer)); } @@ -1646,6 +1651,13 @@ extension on Object { } } +extension on Pointer { + U? convert(U Function(Pointer) converter) { + if (this == nullptr) return null; + return converter(this); + } +} + extension on List { AuthProviderType fromIndex(int index) { if (!AuthProviderType.values.any((value) => value.index == index)) { diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index b19136b1e..2af07ffbe 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -18,9 +18,9 @@ import 'dart:collection'; -import 'realm_class.dart'; - import 'native/realm_core.dart'; +import 'realm_class.dart'; +import 'util.dart'; class Subscription { final SubscriptionHandle _handle; @@ -57,11 +57,11 @@ abstract class SubscriptionSet with IterableMixin { SubscriptionSet._(this._realm, this._handle); Subscription? find(RealmResults query) { - return Subscription._(realmCore.findSubscriptionByQuery(this, query)); + return realmCore.findSubscriptionByQuery(this, query).convert(Subscription._); } Subscription? findByName(String name) { - return Subscription._(realmCore.findSubscriptionByName(this, name)); + return realmCore.findSubscriptionByName(this, name).convert(Subscription._); } Future waitForStateChange(SubscriptionSetState state) async { @@ -76,6 +76,8 @@ abstract class SubscriptionSet with IterableMixin { return Subscription._(realmCore.subscriptionAt(this, index)); } + Subscription operator[](int index) => elementAt(index); + @override _SubscriptionIterator get iterator => _SubscriptionIterator._(this); diff --git a/lib/src/util.dart b/lib/src/util.dart new file mode 100644 index 000000000..34169a42c --- /dev/null +++ b/lib/src/util.dart @@ -0,0 +1,25 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +extension NullableExtension on T? { + U? convert(U Function(T) converter) { + final self = this; + if (self == null) return null; + return converter(self); + } +} \ No newline at end of file diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 59b703338..f2c2a1145 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -53,8 +53,10 @@ Future main([List? args]) async { expect(() => realm.subscriptions, throws()); }); - testSubscriptions('SubscriptionSet.state', (realm) { - expect(realm.subscriptions.state, SubscriptionSetState.uncommitted); + testSubscriptions('SubscriptionSet.state', (realm) async { + final subscriptions = realm.subscriptions; + await subscriptions.waitForStateChange(SubscriptionSetState.complete); + expect(subscriptions.state, SubscriptionSetState.complete); }); testSubscriptions('SubscriptionSet.version', (realm) async { @@ -76,6 +78,82 @@ Future main([List? args]) async { expect(subscriptions.version, 2); }); + testSubscriptions('SubscriptionSet.add', (realm) { + final subscriptions = realm.subscriptions; + final query = realm.all(); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(query); + }); + expect(subscriptions, isNotEmpty); + expect(subscriptions.find(query), isNotNull); + }); + + testSubscriptions('SubscriptionSet.add (named)', (realm) { + final subscriptions = realm.subscriptions; + + const name = 'some name'; + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.all(), name: name); + }); + expect(subscriptions, isNotEmpty); + expect(subscriptions.findByName(name), isNotNull); + }); + + testSubscriptions('SubscriptionSet.find', (realm) { + final subscriptions = realm.subscriptions; + final query = realm.all(); + + expect(subscriptions.find(query), isNull); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(query); + }); + expect(subscriptions.find(query), isNotNull); + }); + + testSubscriptions('SubscriptionSet.find (named)', (realm) { + final subscriptions = realm.subscriptions; + + const name = 'some name'; + expect(subscriptions.findByName(name), isNull); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.all(), name: name); + }); + expect(subscriptions.findByName(name), isNotNull); + }); + + testSubscriptions('SubscriptionSet.remove', (realm) { + final subscriptions = realm.subscriptions; + final query = realm.all(); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(query); + }); + expect(subscriptions, isNotEmpty); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.remove(query); + }); + expect(subscriptions, isEmpty); + }); + + testSubscriptions('SubscriptionSet.remove (named)', (realm) { + final subscriptions = realm.subscriptions; + + const name = 'some name'; + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.all(), name: name); + }); + expect(subscriptions, isNotEmpty); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.removeByName(name); + }); + expect(subscriptions, isEmpty); + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; From a0f2c195cf4cf861c1026bf91770fbb0821a03cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 2 May 2022 12:57:21 +0200 Subject: [PATCH 013/122] More tests --- test/subscription_test.dart | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index f2c2a1145..f510cda32 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -154,6 +154,39 @@ Future main([List? args]) async { expect(subscriptions, isEmpty); }); + testSubscriptions('SubscriptionSet.removeAll', (realm) { + final subscriptions = realm.subscriptions; + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.query(r'_id == $0', [ObjectId()])); + mutableSubscriptions.addOrUpdate(realm.all()); + }); + expect(subscriptions.length, 2); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.removeAll(); + }); + expect(subscriptions, isEmpty); + }); + + testSubscriptions('SubscriptionSet.waitForStateChange', (realm) async { + final subscriptions = realm.subscriptions; + await subscriptions.waitForStateChange(SubscriptionSetState.complete); + + final stateMachineSteps = [ + subscriptions.waitForStateChange(SubscriptionSetState.uncommitted), + subscriptions.waitForStateChange(SubscriptionSetState.pending), + subscriptions.waitForStateChange(SubscriptionSetState.bootstrapping), + subscriptions.waitForStateChange(SubscriptionSetState.complete), + ]; + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.all()); + }); + + await Future.wait(stateMachineSteps); + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; From eb654b8be4b52d141dd6410ad76efdfca14f2ca2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 2 May 2022 14:31:48 +0200 Subject: [PATCH 014/122] Add elementAt test --- lib/src/native/realm_core.dart | 3 ++- lib/src/subscription.dart | 16 ++++++++++++++-- src/subscription_set.cpp | 2 -- test/subscription_test.dart | 21 +++++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 399f5e061..f78c5b405 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -680,7 +680,8 @@ class _RealmCore { bool objectEquals(RealmObject first, RealmObject 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); + RealmResultsHandle resultsSnapshot(RealmResults results) { final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_snapshot(results.handle._pointer)); return RealmResultsHandle._(resultsPointer); diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 2af07ffbe..7af23122b 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -26,6 +26,18 @@ class Subscription { final SubscriptionHandle _handle; Subscription._(this._handle); + + @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); + } +} + +extension SubscriptionInternal on Subscription { + SubscriptionHandle get handle => _handle; } class _SubscriptionIterator implements Iterator { @@ -76,7 +88,7 @@ abstract class SubscriptionSet with IterableMixin { return Subscription._(realmCore.subscriptionAt(this, index)); } - Subscription operator[](int index) => elementAt(index); + Subscription operator [](int index) => elementAt(index); @override _SubscriptionIterator get iterator => _SubscriptionIterator._(this); @@ -117,7 +129,7 @@ class MutableSubscriptionSet extends SubscriptionSet { } } - bool addOrUpdate(RealmResults query, {String? name, bool update = true}) { + bool addOrUpdate(RealmResults query, {String? name}) { assert(_mutableHandle != null); return realmCore.insertOrAssignSubscription(this, query, name); } diff --git a/src/subscription_set.cpp b/src/subscription_set.cpp index c63792256..2c8ca1a15 100644 --- a/src/subscription_set.cpp +++ b/src/subscription_set.cpp @@ -18,8 +18,6 @@ #include "subscription_set.h" -#include - #include #include #include diff --git a/test/subscription_test.dart b/test/subscription_test.dart index f510cda32..453fc2caf 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -169,6 +169,27 @@ Future main([List? args]) async { expect(subscriptions, isEmpty); }); + testSubscriptions('SubscriptionSet.elementAt', (realm) { + final subscriptions = realm.subscriptions; + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.addOrUpdate(realm.query(r'_id == $0', [ObjectId()])); + mutableSubscriptions.addOrUpdate(realm.all()); + }); + expect(subscriptions.length, 2); + + int index = 0; + for (final s in subscriptions) { + expect(s, s); + expect(subscriptions[index], isNotNull); + /* TODO: Not posible yet + expect(subscriptions[index], subscriptions[index]); + expect(s, subscriptions[index]); + */ + ++index; + } + }); + testSubscriptions('SubscriptionSet.waitForStateChange', (realm) async { final subscriptions = realm.subscriptions; await subscriptions.waitForStateChange(SubscriptionSetState.complete); From efcde2fd1d24a165a73de934e71cbe0774c05221 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Mon, 2 May 2022 23:43:08 +0200 Subject: [PATCH 015/122] Fix windows build --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48a6023f3..b6294c408 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,15 @@ set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) set(CMAKE_POSITION_INDEPENDENT_CODE ON) set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") +# Needed for Win32 headers we might pull in +if(CMAKE_SYSTEM_NAME MATCHES "^Windows") + add_compile_definitions( + WIN32_LEAN_AND_MEAN # include minimal Windows.h for faster builds + UNICODE # prefer Unicode variants of Windows APIs over ANSI variants + _UNICODE # prefer Unicode variants of C runtime APIs over ANSI variants + ) +endif() + set(_SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING=1) add_subdirectory(src) \ No newline at end of file From b8d78d55f0ad30e1e991b83bd7e9b05781b07581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 5 May 2022 09:08:54 +0200 Subject: [PATCH 016/122] Add subscription tests to flutter tests * Add subscription tests to test_driver/realm_test.dart * Allow network use from test_driver app on macos --- .../realm_flutter/tests/macos/Runner/DebugProfile.entitlements | 2 ++ flutter/realm_flutter/tests/test_driver/realm_test.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/flutter/realm_flutter/tests/macos/Runner/DebugProfile.entitlements b/flutter/realm_flutter/tests/macos/Runner/DebugProfile.entitlements index dddb8a30c..08c3ab17c 100644 --- a/flutter/realm_flutter/tests/macos/Runner/DebugProfile.entitlements +++ b/flutter/realm_flutter/tests/macos/Runner/DebugProfile.entitlements @@ -8,5 +8,7 @@ com.apple.security.network.server + com.apple.security.network.client + diff --git a/flutter/realm_flutter/tests/test_driver/realm_test.dart b/flutter/realm_flutter/tests/test_driver/realm_test.dart index 9431fd24b..3c57839ba 100644 --- a/flutter/realm_flutter/tests/test_driver/realm_test.dart +++ b/flutter/realm_flutter/tests/test_driver/realm_test.dart @@ -14,6 +14,7 @@ import '../test/results_test.dart' as results_tests; import '../test/credentials_test.dart' as credentials_tests; import '../test/app_test.dart' as app_tests; import '../test/user_test.dart' as user_tests; +import '../test/subscription_test.dart' as subscription_test; Future main(List args) async { final Completer completer = Completer(); @@ -27,6 +28,7 @@ Future main(List args) async { await credentials_tests.main(args); await app_tests.main(args); await user_tests.main(args); + await subscription_test.main(args); tearDown(() { if (Invoker.current?.liveTest.state.result == test_api.Result.error || Invoker.current?.liveTest.state.result == test_api.Result.failure) { From 9b699f54a796ea48f1e13c658acb903cdf70fb69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 5 May 2022 09:23:50 +0200 Subject: [PATCH 017/122] Update core to c9c9f5e62 (v11.15.0-8-gc9c9f5e62) --- lib/src/native/realm_bindings.dart | 108 ++++++++++++++++++++++++----- src/realm-core | 2 +- 2 files changed, 93 insertions(+), 17 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 3e64097dc..f3335353f 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -1168,6 +1168,74 @@ class RealmLibrary { late final _realm_open = _realm_openPtr .asFunction Function(ffi.Pointer)>(); + /// The overloaded Realm::convert function offers a way to copy and/or convert a realm. + /// + /// The following options are supported: + /// - local -> local (config or path) + /// - local -> sync (config only) + /// - sync -> local (config only) + /// - sync -> sync (config or path) + /// - sync -> bundlable sync (client file identifier removed) + /// + /// Note that for bundled realms it is required that all local changes are synchronized with the + /// server before the copy can be written. This is to be sure that the file can be used as a + /// stating point for a newly installed application. The function will throw if there are + /// pending uploads. + /// / + /// /** + /// Copy or convert a Realm using a config. + /// + /// If the file already exists, data will be copied over object per object. + /// If the file does not exist, the realm file will be exported to the new location and if the + /// configuration object contains a sync part, a sync history will be synthesized. + /// + /// @param config The realm configuration that should be used to create a copy. + /// This can be a local or a synced Realm, encrypted or not. + bool realm_convert_with_config( + ffi.Pointer realm, + ffi.Pointer config, + ) { + return _realm_convert_with_config( + realm, + config, + ) != + 0; + } + + late final _realm_convert_with_configPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function(ffi.Pointer, + ffi.Pointer)>>('realm_convert_with_config'); + late final _realm_convert_with_config = + _realm_convert_with_configPtr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + + /// Copy a Realm using a path. + /// + /// @param path The path the realm should be copied to. Local realms will remain local, synced + /// realms will remain synced realms. + /// @param encryption_key The optional encryption key for the new realm. + bool realm_convert_with_path( + ffi.Pointer realm, + ffi.Pointer path, + realm_binary_t encryption_key, + ) { + return _realm_convert_with_path( + realm, + path, + encryption_key, + ) != + 0; + } + + late final _realm_convert_with_pathPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function(ffi.Pointer, ffi.Pointer, + realm_binary_t)>>('realm_convert_with_path'); + late final _realm_convert_with_path = _realm_convert_with_pathPtr.asFunction< + int Function( + ffi.Pointer, ffi.Pointer, realm_binary_t)>(); + /// Deletes the following files for the given `realm_file_path` if they exist: /// - the Realm file itself /// - the .management folder @@ -7216,7 +7284,7 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer)>(); - /// Wait uptill subscripton set state is equal to the state passed as parameter. + /// Wait until subscripton set state is equal to the state passed as parameter. /// This is a blocking operation. /// @return the current subscription state int realm_sync_on_subscription_set_state_change_wait( @@ -7241,27 +7309,39 @@ class RealmLibrary { /// This is an asynchronous operation. /// @return true/false if the handler was registered correctly bool realm_sync_on_subscription_set_state_change_async( - ffi.Pointer arg0, - int arg1, - realm_sync_on_subscription_state_changed arg2, + ffi.Pointer subscription_set, + int notify_when, + realm_sync_on_subscription_state_changed callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, ) { return _realm_sync_on_subscription_set_state_change_async( - arg0, - arg1, - arg2, + subscription_set, + notify_when, + callback, + userdata, + userdata_free, ) != 0; } late final _realm_sync_on_subscription_set_state_change_asyncPtr = _lookup< ffi.NativeFunction< - ffi.Uint8 Function(ffi.Pointer, - ffi.Int32, realm_sync_on_subscription_state_changed)>>( + ffi.Uint8 Function( + ffi.Pointer, + ffi.Int32, + realm_sync_on_subscription_state_changed, + ffi.Pointer, + realm_free_userdata_func_t)>>( 'realm_sync_on_subscription_set_state_change_async'); late final _realm_sync_on_subscription_set_state_change_async = _realm_sync_on_subscription_set_state_change_asyncPtr.asFunction< - int Function(ffi.Pointer, int, - realm_sync_on_subscription_state_changed)>(); + int Function( + ffi.Pointer, + int, + realm_sync_on_subscription_state_changed, + ffi.Pointer, + realm_free_userdata_func_t)>(); /// Retrieve version for the subscription set passed as parameter /// @return subscription set version if the poiter to the subscription is valid @@ -9066,8 +9146,6 @@ abstract class realm_sync_errno_session { static const int RLM_SYNC_ERR_SESSION_BAD_CLIENT_VERSION = 210; static const int RLM_SYNC_ERR_SESSION_DIVERGING_HISTORIES = 211; static const int RLM_SYNC_ERR_SESSION_BAD_CHANGESET = 212; - static const int RLM_SYNC_ERR_SESSION_SUPERSEDED = 213; - static const int RLM_SYNC_ERR_SESSION_DISABLED_SESSION = 213; static const int RLM_SYNC_ERR_SESSION_PARTIAL_SYNC_DISABLED = 214; static const int RLM_SYNC_ERR_SESSION_UNSUPPORTED_SESSION_FEATURE = 215; static const int RLM_SYNC_ERR_SESSION_BAD_ORIGIN_FILE_IDENT = 216; @@ -9161,9 +9239,7 @@ typedef realm_sync_ssl_verify_func_t = ffi.Pointer< ffi.Int32)>>; typedef realm_flx_sync_subscription_set_t = realm_flx_sync_subscription_set; typedef realm_sync_on_subscription_state_changed = ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, ffi.Int32)>>; + ffi.NativeFunction, ffi.Int32)>>; typedef realm_flx_sync_subscription_t = realm_flx_sync_subscription; typedef realm_flx_sync_mutable_subscription_set_t = realm_flx_sync_mutable_subscription_set; diff --git a/src/realm-core b/src/realm-core index 472d325a2..c9c9f5e62 160000 --- a/src/realm-core +++ b/src/realm-core @@ -1 +1 @@ -Subproject commit 472d325a2e844fcb48c400db74e6b9646f92ca93 +Subproject commit c9c9f5e6228bbf8868587cdfe1feaae2d0666303 From 547d6508667ae351a695a99a211cc0921fb62678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 5 May 2022 19:01:18 +0200 Subject: [PATCH 018/122] Use C-API in realm_dart_sync_on_subscription_set_state_change_async --- src/subscription_set.cpp | 49 ++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/src/subscription_set.cpp b/src/subscription_set.cpp index 2c8ca1a15..1120cd373 100644 --- a/src/subscription_set.cpp +++ b/src/subscription_set.cpp @@ -18,38 +18,43 @@ #include "subscription_set.h" -#include -#include #include +#include +#include #include "event_loop_dispatcher.hpp" -namespace realm::c_api { +namespace realm::c_api { +namespace { using namespace realm::sync; +using FreeT = std::function; +using CallbackT = std::function; // Differs per callback +using UserdataT = std::tuple; + +void _callback(void *userdata, realm_flx_sync_subscription_set_state state) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(state); +} + +void _userdata_free(void *userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + RLM_API bool realm_dart_sync_on_subscription_set_state_change_async( - const realm_flx_sync_subscription_set_t* subscription_set, + const realm_flx_sync_subscription_set_t *subscription_set, realm_flx_sync_subscription_set_state_e notify_when, - realm_dart_sync_on_subscription_state_changed callback, - void* userdata, + realm_sync_on_subscription_state_changed callback, void *userdata, realm_free_userdata_func_t userdata_free, - realm_scheduler_t* scheduler) noexcept + realm_scheduler_t *scheduler) noexcept { - return wrap_err([&]() { - auto future_state = subscription_set->get_state_change_notification(SubscriptionSet::State{notify_when}); - std::move(future_state) - .get_async([callback, scheduler, userdata = SharedUserdata(userdata, util::DispatchFreeUserdata(*scheduler, userdata_free))](const StatusWith& state) -> void { - auto cb = util::EventLoopDispatcher{*scheduler, callback}; - if (state.is_ok()) { - cb(userdata.get(), realm_flx_sync_subscription_set_state_e(static_cast(state.get_value()))); - } - else { - cb(userdata.get(), realm_flx_sync_subscription_set_state_e::RLM_SYNC_SUBSCRIPTION_ERROR); - } - }); - return true; - }); + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{*scheduler, callback}, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{*scheduler, userdata_free}, userdata)); + return realm_sync_on_subscription_set_state_change_async(subscription_set, notify_when, _callback, u, _userdata_free); } -} // namespace realm::c_api +} // anonymous namespace +} // namespace realm::c_api From a55c85df44a55c1224a9ef32a32f2fe834c58ce6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 5 May 2022 19:01:45 +0200 Subject: [PATCH 019/122] Prevent downcast of Realm.subscriptions to MutableSubscriptionSet --- lib/src/native/realm_core.dart | 11 ++++------- lib/src/subscription.dart | 35 ++++++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index f78c5b405..8e5422786 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -153,6 +153,10 @@ class _RealmCore { _realmLib.realm_config_set_path(configHandle._pointer, config.path.toUtf8Ptr(arena)); _realmLib.realm_config_set_scheduler(configHandle._pointer, schedulerHandle._pointer); + if (config.fifoFilesFallbackPath != null) { + _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(arena)); + } + if (config is LocalConfiguration) { if (config.initialDataCallback != null) { _realmLib.realm_config_set_data_initialization_function( @@ -165,9 +169,6 @@ class _RealmCore { // TODO: Get rid of this _realmLib.realm_config_set_in_memory(configHandle._pointer, config.isInMemory); } - if (config.fifoFilesFallbackPath != null) { - _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(arena)); - } if (config.disableFormatUpgrade) { _realmLib.realm_config_set_disable_format_upgrade(configHandle._pointer, config.disableFormatUpgrade); } @@ -241,10 +242,6 @@ class _RealmCore { completer.complete(state); } - void waitForSubscriptionSetStateChangeSync(SubscriptionSet subscriptions, SubscriptionSetState state) { - _realmLib.realm_sync_on_subscription_set_state_change_wait(subscriptions.handle._pointer, state.index); - } - Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen) { final completer = Completer(); _realmLib.realm_dart_sync_on_subscription_set_state_change_async( diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 7af23122b..4272fea06 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -104,52 +104,55 @@ extension SubscriptionSetInternal on SubscriptionSet { Realm get realm => _realm; SubscriptionSetHandle get handle => _handle; - static SubscriptionSet create(Realm realm, SubscriptionSetHandle handle) => MutableSubscriptionSet._(realm, handle); + static SubscriptionSet create(Realm realm, SubscriptionSetHandle handle) => _ImmutableSubscriptionSet._(realm, handle); } -class MutableSubscriptionSet extends SubscriptionSet { - MutableSubscriptionSetHandle? _mutableHandle; - - MutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle) : super._(realm, handle); +class _ImmutableSubscriptionSet extends SubscriptionSet { + _ImmutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle) : super._(realm, handle); @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { - assert(_mutableHandle == null); + final mutableSubscriptions = MutableSubscriptionSet._(realm, _handle, realmCore.makeSubscriptionSetMutable(this)); var commit = false; try { - _mutableHandle = realmCore.makeSubscriptionSetMutable(this); - action(this); + action(mutableSubscriptions); commit = true; } finally { if (commit) { - _handle = realmCore.subscriptionSetCommit(this); + _handle = realmCore.subscriptionSetCommit(mutableSubscriptions); } - // _mutableHandle.release(); // TODO: Release early - _mutableHandle = null; + // _mutableHandle.release(); // TODO: Release early (awaiting refactored handles) } } +} + +class MutableSubscriptionSet extends SubscriptionSet { + final MutableSubscriptionSetHandle _mutableHandle; + + MutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle, this._mutableHandle) : super._(realm, handle); + + @override + void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { + action(this); // or should we just throw? + } bool addOrUpdate(RealmResults query, {String? name}) { - assert(_mutableHandle != null); return realmCore.insertOrAssignSubscription(this, query, name); } void remove(RealmResults query) { - assert(_mutableHandle != null); return realmCore.eraseSubscriptionByQuery(this, query); } void removeByName(String name) { - assert(_mutableHandle != null); return realmCore.eraseSubscriptionByName(this, name); } void removeAll() { - assert(_mutableHandle != null); return realmCore.clearSubscriptionSet(this); } } extension MutableSubscriptionSetInternal on MutableSubscriptionSet { - MutableSubscriptionSetHandle get mutableHandle => _mutableHandle!; + MutableSubscriptionSetHandle get mutableHandle => _mutableHandle; } From ed3c4c9c16bf63c35e89de9d7ed75db446747236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 5 May 2022 19:13:23 +0200 Subject: [PATCH 020/122] Remove lifetime hack --- lib/src/native/realm_core.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 8e5422786..9c1e1d2ae 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -980,10 +980,8 @@ class _RealmCore { }); } - final doNotDie = Set(); AppHandle getApp(AppConfiguration configuration) { final httpTransportHandle = _createHttpTransport(configuration.httpClient); - doNotDie.add(httpTransportHandle); final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); final syncClientConfigHandle = _createSyncClientConfig(configuration); final realmAppPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_get(appConfigHandle._pointer, syncClientConfigHandle._pointer)); From 6632aed78dae2fd406447a76c83dac6a5eea4139 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 9 May 2022 10:49:33 +0200 Subject: [PATCH 021/122] Fix PR feedback --- lib/src/configuration.dart | 70 +++++++++++++--------------------- lib/src/native/realm_core.dart | 29 ++++++-------- lib/src/realm_class.dart | 3 +- lib/src/subscription.dart | 27 ++++++------- src/event_loop_dispatcher.hpp | 2 +- src/subscription_set.cpp | 27 ++++++------- test/configuration_test.dart | 4 +- test/subscription_test.dart | 26 ++----------- 8 files changed, 74 insertions(+), 114 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 285857df1..393194210 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -24,11 +24,8 @@ import 'native/realm_core.dart'; import 'realm_class.dart'; abstract class Configuration { - /// The default filename of a [Realm] database - static const String defaultRealmName = "default.realm"; - static String _initDefaultPath() { - var path = defaultRealmName; + var path = "default.realm"; if (Platform.isAndroid || Platform.isIOS) { path = _path.join(realmCore.getFilesPath(), path); } @@ -50,10 +47,22 @@ abstract class Configuration { return Directory.current.absolute.path; } + /// Specifies the FIFO special files fallback location. + /// + /// Opening a [Realm] creates a number of FIFO special files in order to + /// coordinate access to the [Realm] across threads and processes. If the [Realm] file is stored in a location + /// that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the [Realm] cannot be opened. + /// In that case [Realm] needs a different location to store these files and this property defines that location. + /// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined + /// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files. String? get fifoFilesFallbackPath; + /// The path where the Realm should be stored. + /// + /// If omitted the [defaultPath] for the platform will be used. String get path; + + /// The [RealmSchema] for this [Configuration] RealmSchema get schema; - int get schemaVersion; List? get encryptionKey; @Deprecated('Use Configuration.local instead') @@ -82,7 +91,6 @@ abstract class Configuration { factory Configuration.inMemory( List schemaObjects, String identifier, { - Function(Realm realm)? initialDataCallback, int schemaVersion, String? fifoFilesFallbackPath, String? path, @@ -91,8 +99,6 @@ abstract class Configuration { factory Configuration.flexibleSync( User user, List schemaObjects, { - Function(Realm realm)? initialDataCallback, - int schemaVersion, String? fifoFilesFallbackPath, String? path, }) = FlexibleSyncConfiguration; @@ -100,44 +106,22 @@ abstract class Configuration { /// Configuration used to create a [Realm] instance /// {@category Configuration} -class _ConfigurationBase implements Configuration { +abstract class _ConfigurationBase implements Configuration { /// Creates a [Configuration] with schema objects for opening a [Realm]. _ConfigurationBase( List schemaObjects, { String? path, this.fifoFilesFallbackPath, - this.schemaVersion = 0, this.encryptionKey, }) : schema = RealmSchema(schemaObjects), path = path ?? Configuration.defaultPath; - /// The [RealmSchema] for this [Configuration] @override final RealmSchema schema; - /// The schema version used to open the [Realm] - /// - /// If omitted the default value of `0` is used to open the [Realm] - /// It is required to specify a schema version when initializing an existing - /// Realm with a schema that contains objects that differ from their previous - /// specification. If the schema was updated and the schemaVersion was not, - /// an [RealmException] will be thrown. - @override - final int schemaVersion; - - /// The path where the Realm should be stored. - /// - /// If omitted the [defaultPath] for the platform will be used. @override final String path; - /// Specifies the FIFO special files fallback location. - /// Opening a [Realm] creates a number of FIFO special files in order to - /// coordinate access to the [Realm] across threads and processes. If the [Realm] file is stored in a location - /// that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the [Realm] cannot be opened. - /// In that case [Realm] needs a different location to store these files and this property defines that location. - /// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined - /// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files. @override final String? fifoFilesFallbackPath; @@ -149,20 +133,28 @@ class LocalConfiguration extends _ConfigurationBase { LocalConfiguration( List schemaObjects, { this.initialDataCallback, - int schemaVersion = 0, + this.schemaVersion = 0, String? fifoFilesFallbackPath, String? path, this.disableFormatUpgrade = false, - this.isInMemory = false, this.isReadOnly = false, this.shouldCompactCallback, }) : super( schemaObjects, path: path, fifoFilesFallbackPath: fifoFilesFallbackPath, - schemaVersion: schemaVersion, ); + /// The schema version used to open the [Realm] + /// + /// If omitted the default value of `0` is used to open the [Realm] + /// It is required to specify a schema version when initializing an existing + /// Realm with a schema that contains objects that differ from their previous + /// specification. If the schema was updated and the schemaVersion was not, + /// an [RealmException] will be thrown. + @override + final int schemaVersion; + /// Specifies whether a [Realm] should be opened as read-only. /// This allows opening it from locked locations such as resources, /// bundled with an application. @@ -176,8 +168,6 @@ class LocalConfiguration extends _ConfigurationBase { /// The file will also be used as swap space if the [Realm] becomes bigger than what fits in memory, /// but it is not persistent and will be removed when the last instance is closed. /// When all in-memory instance of [Realm] is closed all data in that [Realm] is deleted. - final bool isInMemory; // TODO: Get rid of this! - /// Specifies if a [Realm] file format should be automatically upgraded /// if it was created with an older version of the [Realm] library. /// An exception will be thrown if a file format upgrade is required. @@ -206,17 +196,16 @@ class _SyncConfigurationBase extends _ConfigurationBase { _SyncConfigurationBase( this.user, List schemaObjects, { - int schemaVersion = 0, String? fifoFilesFallbackPath, String? path, }) : super( schemaObjects, - schemaVersion: schemaVersion, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, ); } +/// @nodoc enum SessionStopPolicy { immediately, // Immediately stop the session as soon as all Realms/Sessions go out of scope. liveIndefinitely, // Never stop the session. @@ -229,14 +218,11 @@ class FlexibleSyncConfiguration extends _SyncConfigurationBase { FlexibleSyncConfiguration( User user, List schemaObjects, { - Function(Realm realm)? initialDataCallback, - int schemaVersion = 0, String? fifoFilesFallbackPath, String? path, }) : super( user, schemaObjects, - schemaVersion: schemaVersion, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, ); @@ -251,13 +237,11 @@ class InMemoryConfiguration extends _ConfigurationBase { InMemoryConfiguration( List schemaObjects, this.identifier, { - Function(Realm realm)? initialDataCallback, int schemaVersion = 0, String? fifoFilesFallbackPath, String? path, }) : super( schemaObjects, - schemaVersion: schemaVersion, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, ); diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 9c1e1d2ae..0990611ca 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -149,7 +149,6 @@ class _RealmCore { final configHandle = ConfigHandle._(configPtr); _realmLib.realm_config_set_schema(configHandle._pointer, schemaHandle._pointer); - _realmLib.realm_config_set_schema_version(configHandle._pointer, config.schemaVersion); _realmLib.realm_config_set_path(configHandle._pointer, config.path.toUtf8Ptr(arena)); _realmLib.realm_config_set_scheduler(configHandle._pointer, schedulerHandle._pointer); @@ -157,6 +156,10 @@ class _RealmCore { _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(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. + _realmLib.realm_config_set_schema_version(configHandle._pointer, config is LocalConfiguration ? config.schemaVersion : 0); + if (config is LocalConfiguration) { if (config.initialDataCallback != null) { _realmLib.realm_config_set_data_initialization_function( @@ -165,10 +168,6 @@ class _RealmCore { if (config.isReadOnly) { _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); } - if (config.isInMemory) { - // TODO: Get rid of this - _realmLib.realm_config_set_in_memory(configHandle._pointer, config.isInMemory); - } if (config.disableFormatUpgrade) { _realmLib.realm_config_set_disable_format_upgrade(configHandle._pointer, config.disableFormatUpgrade); } @@ -232,18 +231,15 @@ class _RealmCore { } static void _stateChangeCallback(Pointer userdata, int state) { - final completer = userdata.toObject>(isPersistent: true); + final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; } - - // TODO: What about errors?! - - completer.complete(state); + completer.complete(SubscriptionSetState.values[state]); } - Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen) { - final completer = Completer(); + Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen) { + final completer = Completer(); _realmLib.realm_dart_sync_on_subscription_set_state_change_async( subscriptions.handle._pointer, notifyWhen.index, @@ -255,16 +251,15 @@ class _RealmCore { return completer.future; } - int subscriptionSetVersion(SubscriptionSet subscriptions) { + int subscriptionSetGetVersion(SubscriptionSet subscriptions) { return _realmLib.realm_sync_subscription_set_version(subscriptions.handle._pointer); } - int subscriptionSetState(SubscriptionSet subscriptions) { - refreshSubscriptionSet(subscriptions); - return _realmLib.realm_sync_subscription_set_state(subscriptions.handle._pointer); + SubscriptionSetState subscriptionSetGetState(SubscriptionSet subscriptions) { + return SubscriptionSetState.values[_realmLib.realm_sync_subscription_set_state(subscriptions.handle._pointer)]; } - MutableSubscriptionSetHandle makeSubscriptionSetMutable(SubscriptionSet subscriptions) { + MutableSubscriptionSetHandle subscriptionSetMakeMutable(SubscriptionSet subscriptions) { return MutableSubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_make_subscription_set_mutable(subscriptions.handle._pointer))); } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 7007db102..edb649402 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -270,8 +270,9 @@ class Realm { SubscriptionSet? _subscriptions; /// The active [subscriptions] for this [Realm] SubscriptionSet get subscriptions { - if (config is! FlexibleSyncConfiguration) throw RealmError('Does not support flexible synchronization'); + if (config is! FlexibleSyncConfiguration) throw RealmError('subscriptions is only valid on Realms opened with a FlexibleSyncConfiguration'); _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); + realmCore.refreshSubscriptionSet(_subscriptions!); return _subscriptions!; } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 4272fea06..eb5a71bef 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:async'; import 'dart:collection'; import 'native/realm_core.dart'; @@ -76,10 +77,14 @@ abstract class SubscriptionSet with IterableMixin { return realmCore.findSubscriptionByName(this, name).convert(Subscription._); } - Future waitForStateChange(SubscriptionSetState state) async { - return SubscriptionSetState.values[await realmCore.waitForSubscriptionSetStateChange(this, state)]; + Future _waitForStateChange(SubscriptionSetState state) async { + final result = await realmCore.waitForSubscriptionSetStateChange(this, state); + realmCore.refreshSubscriptionSet(this); + return result; } + Future waitForSynchronization() => _waitForStateChange(SubscriptionSetState.complete); + @override int get length => realmCore.getSubscriptionSetSize(this); @@ -95,9 +100,9 @@ abstract class SubscriptionSet with IterableMixin { void update(void Function(MutableSubscriptionSet mutableSubscriptions) action); - int get version => realmCore.subscriptionSetVersion(this); + int get version => realmCore.subscriptionSetGetVersion(this); - SubscriptionSetState get state => SubscriptionSetState.values[realmCore.subscriptionSetState(this)]; + SubscriptionSetState get state => realmCore.subscriptionSetGetState(this); } extension SubscriptionSetInternal on SubscriptionSet { @@ -112,17 +117,9 @@ class _ImmutableSubscriptionSet extends SubscriptionSet { @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { - final mutableSubscriptions = MutableSubscriptionSet._(realm, _handle, realmCore.makeSubscriptionSetMutable(this)); - var commit = false; - try { - action(mutableSubscriptions); - commit = true; - } finally { - if (commit) { - _handle = realmCore.subscriptionSetCommit(mutableSubscriptions); - } - // _mutableHandle.release(); // TODO: Release early (awaiting refactored handles) - } + final mutableSubscriptions = MutableSubscriptionSet._(realm, _handle, realmCore.subscriptionSetMakeMutable(this)); + action(mutableSubscriptions); + _handle = realmCore.subscriptionSetCommit(mutableSubscriptions); } } diff --git a/src/event_loop_dispatcher.hpp b/src/event_loop_dispatcher.hpp index 537f61277..83b5a1b53 100644 --- a/src/event_loop_dispatcher.hpp +++ b/src/event_loop_dispatcher.hpp @@ -36,7 +36,7 @@ class EventLoopDispatcher { private: const std::shared_ptr> m_func; - std::shared_ptr m_scheduler; // = util::Scheduler::make_default(); + std::shared_ptr m_scheduler; public: EventLoopDispatcher(std::shared_ptr scheduler, util::UniqueFunction func) diff --git a/src/subscription_set.cpp b/src/subscription_set.cpp index 1120cd373..18a965128 100644 --- a/src/subscription_set.cpp +++ b/src/subscription_set.cpp @@ -33,27 +33,28 @@ using FreeT = std::function; using CallbackT = std::function; // Differs per callback using UserdataT = std::tuple; -void _callback(void *userdata, realm_flx_sync_subscription_set_state state) { - auto u = reinterpret_cast(userdata); - std::get<0>(*u)(state); +void _callback(void* userdata, realm_flx_sync_subscription_set_state state) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(state); } -void _userdata_free(void *userdata) { - auto u = reinterpret_cast(userdata); - std::get<1>(*u)(); - delete u; +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; } RLM_API bool realm_dart_sync_on_subscription_set_state_change_async( - const realm_flx_sync_subscription_set_t *subscription_set, + const realm_flx_sync_subscription_set_t* subscription_set, realm_flx_sync_subscription_set_state_e notify_when, - realm_sync_on_subscription_state_changed callback, void *userdata, + realm_sync_on_subscription_state_changed callback, + void* userdata, realm_free_userdata_func_t userdata_free, - realm_scheduler_t *scheduler) noexcept + realm_scheduler_t* scheduler) noexcept { - auto u = new UserdataT(std::bind(util::EventLoopDispatcher{*scheduler, callback}, userdata, std::placeholders::_1), - std::bind(util::EventLoopDispatcher{*scheduler, userdata_free}, userdata)); - return realm_sync_on_subscription_set_state_change_async(subscription_set, notify_when, _callback, u, _userdata_free); + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_on_subscription_set_state_change_async(subscription_set, notify_when, _callback, u, _userdata_free); } } // anonymous namespace diff --git a/test/configuration_test.dart b/test/configuration_test.dart index b1a5be988..8de58a10c 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -64,10 +64,10 @@ Future main([List? args]) async { }); test('Configuration get/set schema version', () { - final config = Configuration([Car.schema]); + final config = LocalConfiguration([Car.schema]); expect(config.schemaVersion, equals(0)); - final explicitSchemaConfig = Configuration([Car.schema], schemaVersion: 3); + final explicitSchemaConfig = LocalConfiguration([Car.schema], schemaVersion: 3); expect(explicitSchemaConfig.schemaVersion, equals(3)); }); diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 453fc2caf..a4c0994b4 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -55,7 +55,7 @@ Future main([List? args]) async { testSubscriptions('SubscriptionSet.state', (realm) async { final subscriptions = realm.subscriptions; - await subscriptions.waitForStateChange(SubscriptionSetState.complete); + await subscriptions.waitForSynchronization(); expect(subscriptions.state, SubscriptionSetState.complete); }); @@ -190,24 +190,6 @@ Future main([List? args]) async { } }); - testSubscriptions('SubscriptionSet.waitForStateChange', (realm) async { - final subscriptions = realm.subscriptions; - await subscriptions.waitForStateChange(SubscriptionSetState.complete); - - final stateMachineSteps = [ - subscriptions.waitForStateChange(SubscriptionSetState.uncommitted), - subscriptions.waitForStateChange(SubscriptionSetState.pending), - subscriptions.waitForStateChange(SubscriptionSetState.bootstrapping), - subscriptions.waitForStateChange(SubscriptionSetState.complete), - ]; - - subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.all()); - }); - - await Future.wait(stateMachineSteps); - }); - testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; @@ -227,7 +209,7 @@ Future main([List? args]) async { expect(subscriptions, isEmpty); - final name = 'a random name'; + const name = 'a random name'; subscriptions.update((mutableSubscriptions) { mutableSubscriptions.addOrUpdate(query, name: name); }); @@ -239,8 +221,8 @@ Future main([List? args]) async { }); expect(subscriptions, isEmpty); - // expect(realm.subscriptions!.findByName(name), isNull); // TODO + expect(realm.subscriptions.findByName(name), isNull); - await subscriptions.waitForStateChange(SubscriptionSetState.complete); + await subscriptions.waitForSynchronization(); }); } From 0dfa78e34fbe1b512a4b101ccbdf42ee5f43d10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 9 May 2022 16:17:12 +0200 Subject: [PATCH 022/122] Rename addOrUpdate to add and return subscription The add method takes an optional update parameter, that default to false. If true, the add will update the existing subscription if any. Otherwise, it will fail if the subscription exists. --- lib/src/native/realm_core.dart | 41 ++++++++++----- lib/src/results.dart | 3 ++ lib/src/subscription.dart | 21 +++++--- test/subscription_test.dart | 93 +++++++++++++++++++++++++++------- 4 files changed, 118 insertions(+), 40 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 0990611ca..cdf089879 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -264,28 +264,38 @@ class _RealmCore { } SubscriptionSetHandle subscriptionSetCommit(MutableSubscriptionSet subscriptions) { - return SubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_subscription_set_commit(subscriptions.mutableHandle._pointer))); + return SubscriptionSetHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_subscription_set_commit(subscriptions.handle._mutablePointer))); } - bool insertOrAssignSubscription(MutableSubscriptionSet subscriptions, RealmResults query, String? name) { + SubscriptionHandle insertOrAssignSubscription(MutableSubscriptionSet subscriptions, RealmResults query, String? name, bool update) { + if (!update) { + if ((name != null && findSubscriptionByName(subscriptions, name) != null) || // + (name == null && findSubscriptionByQuery(subscriptions, query) != null)) { + throw RealmException('Duplicate subscription $query${name != null ? ' with name: $name' : ''}'); + } + } return using((arena) { final out_index = arena(); final out_inserted = arena(); _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign( - subscriptions.mutableHandle._pointer, + subscriptions.handle._mutablePointer, query.queryHandle._pointer, name?.toUtf8Ptr(arena) ?? nullptr, out_index, out_inserted, )); - return out_inserted.value > 0; + return subscriptionAt(subscriptions, out_index.value); }); } + String describeQuery(RealmResults query) { + return _realmLib.realm_query_get_description(query.queryHandle._pointer).cast().toDartString(); + } + void eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { using((arena) { _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_name( - subscriptions.mutableHandle._pointer, + subscriptions.handle._mutablePointer, name.toUtf8Ptr(arena), )); }); @@ -293,13 +303,13 @@ class _RealmCore { void eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_query( - subscriptions.mutableHandle._pointer, + subscriptions.handle._mutablePointer, query.queryHandle._pointer, )); } void clearSubscriptionSet(MutableSubscriptionSet subscriptions) { - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_clear(subscriptions.mutableHandle._pointer)); + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_clear(subscriptions.handle._mutablePointer)); } void refreshSubscriptionSet(SubscriptionSet subscriptions) { @@ -1381,10 +1391,9 @@ class RealmQueryHandle extends Handle { RealmQueryHandle._(Pointer pointer) : super(pointer, 256); } -class RealmNotificationTokenHandle extends Handle { +class ReleasableHandle extends Handle { bool released = false; - RealmNotificationTokenHandle._(Pointer pointer) : super(pointer, 32); - + ReleasableHandle(Pointer pointer, int size) : super(pointer, size); void release() { if (released) { return; @@ -1396,6 +1405,10 @@ class RealmNotificationTokenHandle extends Handle { } } +class RealmNotificationTokenHandle extends ReleasableHandle { + RealmNotificationTokenHandle._(Pointer pointer) : super(pointer, 32); +} + class RealmCollectionChangesHandle extends Handle { RealmCollectionChangesHandle._(Pointer pointer) : super(pointer, 256); } @@ -1432,12 +1445,14 @@ class SubscriptionHandle extends Handle { SubscriptionHandle._(Pointer pointer) : super(pointer, 24); } -class SubscriptionSetHandle extends Handle { +class SubscriptionSetHandle extends ReleasableHandle { SubscriptionSetHandle._(Pointer pointer) : super(pointer, 24); } -class MutableSubscriptionSetHandle extends Handle { - MutableSubscriptionSetHandle._(Pointer pointer) : super(pointer, 24); +class MutableSubscriptionSetHandle extends SubscriptionSetHandle { + MutableSubscriptionSetHandle._(Pointer pointer) : super._(pointer.cast()); + + Pointer get _mutablePointer => super._pointer.cast(); } extension on List { diff --git a/lib/src/results.dart b/lib/src/results.dart index 131aeb4b8..30225f620 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -77,6 +77,9 @@ class RealmResults extends collection.IterableBase { final controller = ResultsNotificationsController(this); return controller.createStream(); } + + @override + String toString() => realmCore.describeQuery(this); } /// @nodoc diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index eb5a71bef..9414384f6 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -117,24 +117,29 @@ class _ImmutableSubscriptionSet extends SubscriptionSet { @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { - final mutableSubscriptions = MutableSubscriptionSet._(realm, _handle, realmCore.subscriptionSetMakeMutable(this)); - action(mutableSubscriptions); - _handle = realmCore.subscriptionSetCommit(mutableSubscriptions); + final mutableSubscriptions = MutableSubscriptionSet._(realm, realmCore.subscriptionSetMakeMutable(this)); + try { + action(mutableSubscriptions); + _handle = realmCore.subscriptionSetCommit(mutableSubscriptions); + } finally { + // Release as early as possible, as we cannot start new update, until this is released! + mutableSubscriptions._handle.release(); + } } } class MutableSubscriptionSet extends SubscriptionSet { - final MutableSubscriptionSetHandle _mutableHandle; + final MutableSubscriptionSetHandle _handle; - MutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle, this._mutableHandle) : super._(realm, handle); + MutableSubscriptionSet._(Realm realm, this._handle) : super._(realm, _handle); @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { action(this); // or should we just throw? } - bool addOrUpdate(RealmResults query, {String? name}) { - return realmCore.insertOrAssignSubscription(this, query, name); + Subscription add(RealmResults query, {String? name, bool update = false}) { + return Subscription._(realmCore.insertOrAssignSubscription(this, query, name, update)); } void remove(RealmResults query) { @@ -151,5 +156,5 @@ class MutableSubscriptionSet extends SubscriptionSet { } extension MutableSubscriptionSetInternal on MutableSubscriptionSet { - MutableSubscriptionSetHandle get mutableHandle => _mutableHandle; + MutableSubscriptionSetHandle get handle => _handle; } diff --git a/test/subscription_test.dart b/test/subscription_test.dart index a4c0994b4..52eb2ceb5 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -32,13 +32,9 @@ void testSubscriptions(String name, FutureOr Function(Realm) tester) async final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = (Configuration.flexibleSync(user, [Task.schema]) as FlexibleSyncConfiguration)..sessionStopPolicy = SessionStopPolicy.immediately; + final configuration = FlexibleSyncConfiguration(user, [Task.schema])..sessionStopPolicy = SessionStopPolicy.immediately; final realm = getRealm(configuration); - try { - await tester(realm); - } finally { - realm.close(); - } + await tester(realm); }); } @@ -64,7 +60,7 @@ Future main([List? args]) async { expect(subscriptions.version, 0); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.all()); + mutableSubscriptions.add(realm.all()); }); expect(subscriptions.length, 1); @@ -83,7 +79,7 @@ Future main([List? args]) async { final query = realm.all(); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(query); + mutableSubscriptions.add(query); }); expect(subscriptions, isNotEmpty); expect(subscriptions.find(query), isNotNull); @@ -94,7 +90,7 @@ Future main([List? args]) async { const name = 'some name'; subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.all(), name: name); + mutableSubscriptions.add(realm.all(), name: name); }); expect(subscriptions, isNotEmpty); expect(subscriptions.findByName(name), isNotNull); @@ -107,7 +103,7 @@ Future main([List? args]) async { expect(subscriptions.find(query), isNull); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(query); + mutableSubscriptions.add(query); }); expect(subscriptions.find(query), isNotNull); }); @@ -119,7 +115,7 @@ Future main([List? args]) async { expect(subscriptions.findByName(name), isNull); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.all(), name: name); + mutableSubscriptions.add(realm.all(), name: name); }); expect(subscriptions.findByName(name), isNotNull); }); @@ -129,7 +125,7 @@ Future main([List? args]) async { final query = realm.all(); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(query); + mutableSubscriptions.add(query); }); expect(subscriptions, isNotEmpty); @@ -144,7 +140,7 @@ Future main([List? args]) async { const name = 'some name'; subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.all(), name: name); + mutableSubscriptions.add(realm.all(), name: name); }); expect(subscriptions, isNotEmpty); @@ -158,8 +154,8 @@ Future main([List? args]) async { final subscriptions = realm.subscriptions; subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.query(r'_id == $0', [ObjectId()])); - mutableSubscriptions.addOrUpdate(realm.all()); + mutableSubscriptions.add(realm.query(r'_id == $0', [ObjectId()])); + mutableSubscriptions.add(realm.all()); }); expect(subscriptions.length, 2); @@ -173,8 +169,8 @@ Future main([List? args]) async { final subscriptions = realm.subscriptions; subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(realm.query(r'_id == $0', [ObjectId()])); - mutableSubscriptions.addOrUpdate(realm.all()); + mutableSubscriptions.add(realm.query(r'_id == $0', [ObjectId()])); + mutableSubscriptions.add(realm.all()); }); expect(subscriptions.length, 2); @@ -190,6 +186,65 @@ Future main([List? args]) async { } }); + testSubscriptions('MutableSubscriptionSet.elementAt', (realm) { + final subscriptions = realm.subscriptions; + + subscriptions.update((mutableSubscriptions) { + final s = mutableSubscriptions.add(realm.all()); + expect(mutableSubscriptions[0], isNotNull); + expect(s, isNotNull); + expect(mutableSubscriptions.state, SubscriptionSetState.uncommitted); + // expect(mutableSubscriptions[0], s); // TODO: Not posible yet + }); + }); + + testSubscriptions('MutableSubscriptionSet.add double-add throws', (realm) { + final subscriptions = realm.subscriptions; + + // Cannot add same query twice without requesting an update + expect(() { + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(realm.all()); + }); + }, throws('Duplicate subscription')); + + // Okay to add same query under different names + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all(), name: 'foo'); + mutableSubscriptions.add(realm.all(), name: 'bar'); + }); + + expect(subscriptions.length, 2); + + // Cannot add different queries under same name, unless the second + // can update the first. + expect(() { + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all(), name: 'same'); + mutableSubscriptions.add(realm.query(r'_id == $0', [ObjectId()]), name: 'same'); + }); + }, throws('Duplicate subscription')); + }); + + testSubscriptions('MutableSubscriptionSet.add with update flag', (realm) { + final subscriptions = realm.subscriptions; + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(realm.all(), update: true); + }); + + expect(subscriptions.length, 1); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all(), name: 'same'); + mutableSubscriptions.add(realm.query(r'_id == $0', [ObjectId()]), name: 'same', update: true); + }); + + expect(subscriptions.length, 2); + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; @@ -198,7 +253,7 @@ Future main([List? args]) async { final query = realm.all(); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(query); + mutableSubscriptions.add(query); }); expect(subscriptions.length, 1); @@ -211,7 +266,7 @@ Future main([List? args]) async { const name = 'a random name'; subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.addOrUpdate(query, name: name); + mutableSubscriptions.add(query, name: name); }); expect(subscriptions.findByName(name), isNotNull); From c1544e010a6489e495b00c3706f6334d456d4588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Mon, 9 May 2022 17:17:48 +0200 Subject: [PATCH 023/122] Reorder and enhance doc comments --- lib/src/configuration.dart | 82 ++++++++++++++++++++++---------------- lib/src/realm_class.dart | 13 +++++- lib/src/subscription.dart | 56 ++++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 35 deletions(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 393194210..201f706c3 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -23,6 +23,29 @@ import 'package:path/path.dart' as _path; import 'native/realm_core.dart'; import 'realm_class.dart'; +/// The signature of a callback used to determine if compaction +/// should be attempted. +/// +/// The result of the callback decides if the [Realm] should be compacted +/// before being returned to the user. +/// +/// The callback is given two arguments: +/// * the [totalSize] of the realm file (data + free space) in bytes, and +/// * the [usedSize], which is the number bytes used by data in the file. +/// +/// It should return true to indicate that an attempt to compact the file should be made. +/// The compaction will be skipped if another process is currently accessing the realm file. +typedef ShouldCompactCallback = bool Function(int totalSize, int usedSize); + +/// The signature of a callback that will be executed only when the Realm is first created. +/// +/// The Realm instance passed in the callback already has a write transaction opened, so you can +/// add some initial data that your app needs. The function will not execute for existing +/// Realms, even if all objects in the Realm are deleted. +typedef InitialDataCallback = void Function(Realm realm); + +/// Configuration used to create a [Realm] instance +/// {@category Configuration} abstract class Configuration { static String _initDefaultPath() { var path = "default.realm"; @@ -48,7 +71,7 @@ abstract class Configuration { } /// Specifies the FIFO special files fallback location. - /// + /// /// Opening a [Realm] creates a number of FIFO special files in order to /// coordinate access to the [Realm] across threads and processes. If the [Realm] file is stored in a location /// that does not allow the creation of FIFO special files (e.g. FAT32 filesystems), then the [Realm] cannot be opened. @@ -56,6 +79,7 @@ abstract class Configuration { /// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined /// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files. String? get fifoFilesFallbackPath; + /// The path where the Realm should be stored. /// /// If omitted the [defaultPath] for the platform will be used. @@ -68,24 +92,24 @@ abstract class Configuration { @Deprecated('Use Configuration.local instead') factory Configuration( List schemaObjects, { - Function(Realm realm)? initialDataCallback, + InitialDataCallback? initialDataCallback, int schemaVersion, String? fifoFilesFallbackPath, String? path, bool disableFormatUpgrade, bool isReadOnly, - bool Function(int totalSize, int usedSize)? shouldCompactCallback, + ShouldCompactCallback? shouldCompactCallback, }) = LocalConfiguration; factory Configuration.local( List schemaObjects, { - Function(Realm realm)? initialDataCallback, + InitialDataCallback? initialDataCallback, int schemaVersion, String? fifoFilesFallbackPath, String? path, bool disableFormatUpgrade, bool isReadOnly, - bool Function(int totalSize, int usedSize)? shouldCompactCallback, + ShouldCompactCallback? shouldCompactCallback, }) = LocalConfiguration; factory Configuration.inMemory( @@ -104,10 +128,7 @@ abstract class Configuration { }) = FlexibleSyncConfiguration; } -/// Configuration used to create a [Realm] instance -/// {@category Configuration} abstract class _ConfigurationBase implements Configuration { - /// Creates a [Configuration] with schema objects for opening a [Realm]. _ConfigurationBase( List schemaObjects, { String? path, @@ -129,6 +150,9 @@ abstract class _ConfigurationBase implements Configuration { final List? encryptionKey; } +/// [LocalConfiguration] is used to open local [Realm] instances, +/// that are persisted across runs. +/// {@category Configuration} class LocalConfiguration extends _ConfigurationBase { LocalConfiguration( List schemaObjects, { @@ -145,50 +169,34 @@ class LocalConfiguration extends _ConfigurationBase { fifoFilesFallbackPath: fifoFilesFallbackPath, ); - /// The schema version used to open the [Realm] + /// The schema version used to open the [Realm]. If omitted, the default value is `0`. /// - /// If omitted the default value of `0` is used to open the [Realm] /// It is required to specify a schema version when initializing an existing /// Realm with a schema that contains objects that differ from their previous - /// specification. If the schema was updated and the schemaVersion was not, - /// an [RealmException] will be thrown. - @override + /// specification. + /// + /// If the schema was updated and the schemaVersion was not, + /// a [RealmException] will be thrown. final int schemaVersion; /// Specifies whether a [Realm] should be opened as read-only. + /// /// This allows opening it from locked locations such as resources, /// bundled with an application. /// /// The realm file must already exists at [path] final bool isReadOnly; - /// Specifies whether a [Realm] should be opened in-memory. - /// - /// This still requires a [path] (can be the default path) to identify the [Realm] so other processes can open the same [Realm]. - /// The file will also be used as swap space if the [Realm] becomes bigger than what fits in memory, - /// but it is not persistent and will be removed when the last instance is closed. - /// When all in-memory instance of [Realm] is closed all data in that [Realm] is deleted. /// Specifies if a [Realm] file format should be automatically upgraded /// if it was created with an older version of the [Realm] library. /// An exception will be thrown if a file format upgrade is required. final bool disableFormatUpgrade; - /// The function called when opening a Realm for the first time - /// during the life of a process to determine if it should be compacted - /// before being returned to the user. - /// - /// [totalSize] - The total file size (data + free space) - /// [usedSize] - The total bytes used by data in the file. - /// It returns true to indicate that an attempt to compact the file should be made. - /// The compaction will be skipped if another process is currently accessing the realm file. - final bool Function(int totalSize, int usedSize)? shouldCompactCallback; + /// The function will be called when opening a [Realm] for the first time + /// during the life of a process. + final ShouldCompactCallback? shouldCompactCallback; - /// A function that will be executed only when the Realm is first created. - /// - /// The Realm instance passed in the callback already has a write transaction opened, so you can - /// add some initial data that your app needs. The function will not execute for existing - /// Realms, even if all objects in the Realm are deleted. - final Function(Realm realm)? initialDataCallback; + final InitialDataCallback? initialDataCallback; } class _SyncConfigurationBase extends _ConfigurationBase { @@ -212,6 +220,9 @@ enum SessionStopPolicy { afterChangesUploaded, // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. } +/// [FlexibleSyncConfiguration] is used to open [Realm] instances that are synchronized +/// with MongoDB Realm. +/// {@category Configuration} class FlexibleSyncConfiguration extends _SyncConfigurationBase { SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; @@ -233,6 +244,9 @@ extension FlexibleSyncConfigurationInternal on FlexibleSyncConfiguration { set sessionStopPolicy(SessionStopPolicy value) => _sessionStopPolicy = value; } +/// [InMemoryConfiguration] is used to open [Realm] instances that +/// are temporary to running process. +/// {@category Configuration} class InMemoryConfiguration extends _ConfigurationBase { InMemoryConfiguration( List schemaObjects, diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index edb649402..eedd3d97d 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -47,7 +47,17 @@ export 'package:realm_common/realm_common.dart' // always expose with `show` to explicitly control the public API surface export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App; -export "configuration.dart" show Configuration, RealmSchema, SchemaObject, FlexibleSyncConfiguration, LocalConfiguration, InMemoryConfiguration; +export "configuration.dart" + show + Configuration, + FlexibleSyncConfiguration, + InitialDataCallback, + InMemoryConfiguration, + LocalConfiguration, + RealmSchema, + SchemaObject, + ShouldCompactCallback; + export 'credentials.dart' show Credentials, AuthProviderType, EmailPasswordAuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges; export 'realm_object.dart' show RealmEntity, RealmException, RealmObject, RealmObjectChanges; @@ -268,6 +278,7 @@ class Realm { void deleteAll() => deleteMany(all()); SubscriptionSet? _subscriptions; + /// The active [subscriptions] for this [Realm] SubscriptionSet get subscriptions { if (config is! FlexibleSyncConfiguration) throw RealmError('subscriptions is only valid on Realms opened with a FlexibleSyncConfiguration'); diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 9414384f6..4292d1a50 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -63,16 +63,43 @@ enum SubscriptionSetState { superseded, } +/// A collection representing the set of active subscriptions for a [Realm] instance. +/// +/// This is used in combination with [FlexibleSyncConfiguration] to +/// declare the set of queries you want to synchronize with the server. You can access and +/// read the subscription set freely, but mutating it must happen in an [update] +/// block. +/// +/// Any changes to the subscription set will be persisted locally and be available the next +/// time the application starts up - i.e. it's not necessary to subscribe for the same query +/// every time. Updating the subscription set can be done while offline, and only the latest +/// update will be sent to the server whenever connectivity is restored. +/// +/// It is strongly recommended that you batch updates as much as possible and request the +/// dataset your application needs upfront. Updating the set of active subscriptions for a +/// Realm is an expensive operation serverside, even if there's very little data that needs +/// downloading. abstract class SubscriptionSet with IterableMixin { Realm _realm; SubscriptionSetHandle _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. + /// Finds a subscription by query. + /// + /// If the Subscription set does not contain a subscription with the provided query, + /// return null Subscription? find(RealmResults query) { return realmCore.findSubscriptionByQuery(this, query).convert(Subscription._); } + /// Finds an existing [Subscription] in this set by name. + /// + /// If the Subscription set does not contain a subscription with the provided name, + /// return null Subscription? findByName(String name) { return realmCore.findSubscriptionByName(this, name).convert(Subscription._); } @@ -83,6 +110,12 @@ abstract class SubscriptionSet with IterableMixin { return result; } + /// Waits for the server to acknowledge the subscription set and return the matching objects. + /// + /// If the [state] of the subscription set is [SubscriptionSetState.complete] + /// the returned [Future] will complete immediately. If the state is + /// [SubscriptionSetState.error], the returned future will be throw an + /// error. Future waitForSynchronization() => _waitForStateChange(SubscriptionSetState.complete); @override @@ -93,15 +126,25 @@ abstract class SubscriptionSet with IterableMixin { return Subscription._(realmCore.subscriptionAt(this, index)); } + /// Gets the [Subscription] at the specified index in the set. Subscription operator [](int index) => elementAt(index); @override _SubscriptionIterator get iterator => _SubscriptionIterator._(this); + /// Update the subscription set and send the request to the server in the background. + /// + /// Calling [update] is a prerequisite for mutating the subscription set, + /// using a [MutableSubscriptionSet] parsed to [action]. + /// + /// If you want to wait for the server to acknowledge and send back the data that matches the updated + /// subscriptions, use [waitForSynchronization]. void update(void Function(MutableSubscriptionSet mutableSubscriptions) action); + /// Gets the version of the subscription set. int get version => realmCore.subscriptionSetGetVersion(this); + /// Gets the state of the subscription set. SubscriptionSetState get state => realmCore.subscriptionSetGetState(this); } @@ -128,6 +171,7 @@ class _ImmutableSubscriptionSet extends SubscriptionSet { } } +/// A mutable view to a [SubscriptionSet]. Obtained by calling [SubscriptionSet.update]. class MutableSubscriptionSet extends SubscriptionSet { final MutableSubscriptionSetHandle _handle; @@ -138,18 +182,30 @@ class MutableSubscriptionSet extends SubscriptionSet { action(this); // or should we just throw? } + /// Adds a [query] to the set of active subscriptions. + /// + /// The query will be joined via an OR statement with any existing queries for the same type. + /// + /// If a [name] is given, then this will be used to match with any existing query, + /// otherwise the [query] itself is used for matching. + /// + /// If [update] is specified to [true], then any existing query will be replaced. + /// Otherwise a [RealmException] is thrown, in case of duplicates. Subscription add(RealmResults query, {String? name, bool update = false}) { return Subscription._(realmCore.insertOrAssignSubscription(this, query, name, update)); } + /// Remove any [query] from the set that matches. void remove(RealmResults query) { return realmCore.eraseSubscriptionByQuery(this, query); } + /// Remove any [query] from the set that matches by [name] void removeByName(String name) { return realmCore.eraseSubscriptionByName(this, name); } + /// Clear the subscription set. void removeAll() { return realmCore.clearSubscriptionSet(this); } From 0ae91d85e2f8e6ae1d253d283d54ed83256d612a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 10 May 2022 16:26:26 +0200 Subject: [PATCH 024/122] Rework SubscriptionSet.remove* * remove is now called removeByQuery * remove instead takes a Subscription as an argument * removeAll is renamed to clear to match List.clear * all remove* methods now returns a boolean indicating whether a subscription was removed --- lib/src/native/realm_core.dart | 36 ++++++++++++++++++++++------------ lib/src/subscription.dart | 13 ++++++++---- test/subscription_test.dart | 8 ++++---- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index cdf089879..f9e27e18e 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -292,20 +292,32 @@ class _RealmCore { return _realmLib.realm_query_get_description(query.queryHandle._pointer).cast().toDartString(); } - void eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { - using((arena) { - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_name( - subscriptions.handle._mutablePointer, - name.toUtf8Ptr(arena), - )); + bool eraseSubscription(MutableSubscriptionSet subscriptions, Subscription subscription) { + // TODO: Awaiting fix for https://github.com/realm/realm-core/issues/5475 + // Should look something like: + /* + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_subscription( + subscriptions.handle._mutablePointer, + subscription.handle._pointer, + )); + */ + return false; // TEMPORARY!! + } + + bool eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { + return using((arena) { + return _realmLib.realm_sync_subscription_set_erase_by_name( + subscriptions.handle._mutablePointer, + name.toUtf8Ptr(arena), + ); }); } - void eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_query( - subscriptions.handle._mutablePointer, - query.queryHandle._pointer, - )); + bool eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { + return _realmLib.realm_sync_subscription_set_erase_by_query( + subscriptions.handle._mutablePointer, + query.queryHandle._pointer, + ); } void clearSubscriptionSet(MutableSubscriptionSet subscriptions) { @@ -683,7 +695,7 @@ class _RealmCore { 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); - + RealmResultsHandle resultsSnapshot(RealmResults results) { final resultsPointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_snapshot(results.handle._pointer)); return RealmResultsHandle._(resultsPointer); diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 4292d1a50..a971baa50 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -195,19 +195,24 @@ class MutableSubscriptionSet extends SubscriptionSet { return Subscription._(realmCore.insertOrAssignSubscription(this, query, name, update)); } + // TODO: Make this public when C-API is in place (see: https://github.com/realm/realm-core/issues/5475) + bool _remove(Subscription subscription) { + return realmCore.eraseSubscription(this, subscription); + } + /// Remove any [query] from the set that matches. - void remove(RealmResults query) { + bool removeByQuery(RealmResults query) { return realmCore.eraseSubscriptionByQuery(this, query); } /// Remove any [query] from the set that matches by [name] - void removeByName(String name) { + bool removeByName(String name) { return realmCore.eraseSubscriptionByName(this, name); } /// Clear the subscription set. - void removeAll() { - return realmCore.clearSubscriptionSet(this); + void clear() { + realmCore.clearSubscriptionSet(this); } } diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 52eb2ceb5..dd970b68e 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -67,7 +67,7 @@ Future main([List? args]) async { expect(subscriptions.version, 1); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.removeAll(); + mutableSubscriptions.clear(); }); expect(subscriptions.length, 0); @@ -130,7 +130,7 @@ Future main([List? args]) async { expect(subscriptions, isNotEmpty); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.remove(query); + mutableSubscriptions.removeByQuery(query); }); expect(subscriptions, isEmpty); }); @@ -160,7 +160,7 @@ Future main([List? args]) async { expect(subscriptions.length, 2); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.removeAll(); + mutableSubscriptions.clear(); }); expect(subscriptions, isEmpty); }); @@ -259,7 +259,7 @@ Future main([List? args]) async { expect(subscriptions.length, 1); subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.remove(query); + mutableSubscriptions.removeByQuery(query); }); expect(subscriptions, isEmpty); From 852df8a2ec839bdef61af8eafd1b056b8281d86e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 10 May 2022 16:58:02 +0200 Subject: [PATCH 025/122] More PR feedback --- CHANGELOG.md | 1 + dartdoc_options.yaml | 5 ++- lib/src/configuration.dart | 76 +++++++++++----------------------- lib/src/native/realm_core.dart | 3 +- lib/src/subscription.dart | 46 ++++++++++++++------ src/CMakeLists.txt | 10 ++--- test/subscription_test.dart | 30 +++++++------- 7 files changed, 81 insertions(+), 90 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd040d135..1b90ccb42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ * Support user state. ([#525](https://github.com/realm/realm-dart/pull/525)) * Support getting user id and identities. ([#525](https://github.com/realm/realm-dart/pull/525)) * Support user logout. ([#525](https://github.com/realm/realm-dart/pull/525)) +* Support flexible synchronization ([#496](https://github.com/realm/realm-dart/pull/496)) ### Fixed * Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442)) diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml index 026d37a5b..683be5f02 100644 --- a/dartdoc_options.yaml +++ b/dartdoc_options.yaml @@ -12,7 +12,10 @@ dartdoc: "Application": markdown: topic.md name: Application - categoryOrder: ["Realm", "Configuration", "Annotations", "Application"] + "Sync": + markdown: topic.md + name: Sync + categoryOrder: ["Realm", "Configuration", "Annotations", "Application", "Sync"] examplePathPrefix: 'example' # nodoc: ['generator/flutter/ffigen/scripts/src/test/*.g.dart'] showUndocumentedCategories: true diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 201f706c3..534c0902b 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -47,6 +47,14 @@ typedef InitialDataCallback = void Function(Realm realm); /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration { + Configuration._( + List schemaObjects, { + String? path, + this.fifoFilesFallbackPath, + this.encryptionKey, + }) : schema = RealmSchema(schemaObjects), + path = path ?? Configuration.defaultPath; + static String _initDefaultPath() { var path = "default.realm"; if (Platform.isAndroid || Platform.isIOS) { @@ -78,16 +86,17 @@ abstract class Configuration { /// In that case [Realm] needs a different location to store these files and this property defines that location. /// The FIFO special files are very lightweight and the main [Realm] file will still be stored in the location defined /// by the [path] you property. This property is ignored if the directory defined by [path] allow FIFO special files. - String? get fifoFilesFallbackPath; + final String? fifoFilesFallbackPath; /// The path where the Realm should be stored. /// /// If omitted the [defaultPath] for the platform will be used. - String get path; + final String path; /// The [RealmSchema] for this [Configuration] - RealmSchema get schema; - List? get encryptionKey; + final RealmSchema schema; + + final List? encryptionKey; @Deprecated('Use Configuration.local instead') factory Configuration( @@ -115,7 +124,6 @@ abstract class Configuration { factory Configuration.inMemory( List schemaObjects, String identifier, { - int schemaVersion, String? fifoFilesFallbackPath, String? path, }) = InMemoryConfiguration; @@ -128,32 +136,10 @@ abstract class Configuration { }) = FlexibleSyncConfiguration; } -abstract class _ConfigurationBase implements Configuration { - _ConfigurationBase( - List schemaObjects, { - String? path, - this.fifoFilesFallbackPath, - this.encryptionKey, - }) : schema = RealmSchema(schemaObjects), - path = path ?? Configuration.defaultPath; - - @override - final RealmSchema schema; - - @override - final String path; - - @override - final String? fifoFilesFallbackPath; - - @override - final List? encryptionKey; -} - /// [LocalConfiguration] is used to open local [Realm] instances, /// that are persisted across runs. /// {@category Configuration} -class LocalConfiguration extends _ConfigurationBase { +class LocalConfiguration extends Configuration { LocalConfiguration( List schemaObjects, { this.initialDataCallback, @@ -163,7 +149,7 @@ class LocalConfiguration extends _ConfigurationBase { this.disableFormatUpgrade = false, this.isReadOnly = false, this.shouldCompactCallback, - }) : super( + }) : super._( schemaObjects, path: path, fifoFilesFallbackPath: fifoFilesFallbackPath, @@ -192,27 +178,13 @@ class LocalConfiguration extends _ConfigurationBase { /// An exception will be thrown if a file format upgrade is required. final bool disableFormatUpgrade; - /// The function will be called when opening a [Realm] for the first time - /// during the life of a process. + /// Called when opening a [Realm] for the first time, after process start. final ShouldCompactCallback? shouldCompactCallback; + /// Called when opening a [Realm] for the very first time, when db file is created. final InitialDataCallback? initialDataCallback; } -class _SyncConfigurationBase extends _ConfigurationBase { - final User user; - _SyncConfigurationBase( - this.user, - List schemaObjects, { - String? fifoFilesFallbackPath, - String? path, - }) : super( - schemaObjects, - fifoFilesFallbackPath: fifoFilesFallbackPath, - path: path, - ); -} - /// @nodoc enum SessionStopPolicy { immediately, // Immediately stop the session as soon as all Realms/Sessions go out of scope. @@ -223,16 +195,17 @@ enum SessionStopPolicy { /// [FlexibleSyncConfiguration] is used to open [Realm] instances that are synchronized /// with MongoDB Realm. /// {@category Configuration} -class FlexibleSyncConfiguration extends _SyncConfigurationBase { +class FlexibleSyncConfiguration extends Configuration { + final User user; + SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; FlexibleSyncConfiguration( - User user, + this.user, List schemaObjects, { String? fifoFilesFallbackPath, String? path, - }) : super( - user, + }) : super._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, @@ -247,14 +220,13 @@ extension FlexibleSyncConfigurationInternal on FlexibleSyncConfiguration { /// [InMemoryConfiguration] is used to open [Realm] instances that /// are temporary to running process. /// {@category Configuration} -class InMemoryConfiguration extends _ConfigurationBase { +class InMemoryConfiguration extends Configuration { InMemoryConfiguration( List schemaObjects, this.identifier, { - int schemaVersion = 0, String? fifoFilesFallbackPath, String? path, - }) : super( + }) : super._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index f9e27e18e..5756e7fdc 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -269,8 +269,7 @@ class _RealmCore { SubscriptionHandle insertOrAssignSubscription(MutableSubscriptionSet subscriptions, RealmResults query, String? name, bool update) { if (!update) { - if ((name != null && findSubscriptionByName(subscriptions, name) != null) || // - (name == null && findSubscriptionByQuery(subscriptions, query) != null)) { + if (name != null && findSubscriptionByName(subscriptions, name) != null) { throw RealmException('Duplicate subscription $query${name != null ? ' with name: $name' : ''}'); } } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index a971baa50..55fa87e0f 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -54,12 +54,26 @@ class _SubscriptionIterator implements Iterator { bool moveNext() => ++_index < _subscriptions.length; } +/// {@category Sync} enum SubscriptionSetState { + /// This subscription set has not been persisted and has not been sent to the server. + /// This state is only valid for MutableSubscriptionSets uncommitted, + + /// The subscription set has been persisted locally but has not been acknowledged by the server yet. pending, + + /// The server is currently sending the initial state that represents this subscription set to the client. bootstrapping, + + /// This subscription set is the active subscription set that is currently being synchronized with the server. complete, + + /// An error occurred while processing this subscription set on the server. Check error_str() for details. error, + + /// The server responded to a later subscription set to this one and this one has been + /// trimmed from the local storage of subscription sets. superseded, } @@ -79,6 +93,7 @@ enum SubscriptionSetState { /// dataset your application needs upfront. Updating the set of active subscriptions for a /// Realm is an expensive operation serverside, even if there's very little data that needs /// downloading. +/// {@category Sync} abstract class SubscriptionSet with IterableMixin { Realm _realm; SubscriptionSetHandle _handle; @@ -114,9 +129,14 @@ abstract class SubscriptionSet with IterableMixin { /// /// If the [state] of the subscription set is [SubscriptionSetState.complete] /// the returned [Future] will complete immediately. If the state is - /// [SubscriptionSetState.error], the returned future will be throw an + /// [SubscriptionSetState.error], the returned future will throw an /// error. - Future waitForSynchronization() => _waitForStateChange(SubscriptionSetState.complete); + Future waitForSynchronization() async { + final result = await _waitForStateChange(SubscriptionSetState.complete); + if (result == SubscriptionSetState.error) { + throw RealmException('Synchronization Failed'); + } + } @override int get length => realmCore.getSubscriptionSetSize(this); @@ -172,6 +192,7 @@ class _ImmutableSubscriptionSet extends SubscriptionSet { } /// A mutable view to a [SubscriptionSet]. Obtained by calling [SubscriptionSet.update]. +/// {@category Sync} class MutableSubscriptionSet extends SubscriptionSet { final MutableSubscriptionSetHandle _handle; @@ -179,18 +200,19 @@ class MutableSubscriptionSet extends SubscriptionSet { @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { - action(this); // or should we just throw? + action(this); } - /// Adds a [query] to the set of active subscriptions. - /// - /// The query will be joined via an OR statement with any existing queries for the same type. - /// - /// If a [name] is given, then this will be used to match with any existing query, + /// Adds a [query] to the set of active subscriptions. + /// + /// The query will be joined via an OR statement with any existing queries for the same type. + /// + /// If a [name] is given, then this will be used to match with any existing query, /// otherwise the [query] itself is used for matching. - /// - /// If [update] is specified to [true], then any existing query will be replaced. + /// + /// 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)); } @@ -200,12 +222,12 @@ class MutableSubscriptionSet extends SubscriptionSet { return realmCore.eraseSubscription(this, subscription); } - /// Remove any [query] from the set that matches. + /// Remove the [query] from the set that matches, if any. bool removeByQuery(RealmResults query) { return realmCore.eraseSubscriptionByQuery(this, query); } - /// Remove any [query] from the set that matches by [name] + /// Remove the [query] from the set that matches by [name], if any bool removeByName(String name) { return realmCore.eraseSubscriptionByName(this, name); } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 48cd8def5..c26cae3e7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,9 +8,9 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp - subscription_set.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp ) set(HEADERS @@ -35,10 +35,6 @@ else() target_link_options(realm_dart PRIVATE LINKER:--whole-archive $ LINKER:--no-whole-archive) endif() -if(REALM_ENABLE_SYNC) - target_compile_definitions(realm_dart PUBLIC REALM_ENABLE_SYNC=1) -endif() - string(APPEND OUTPUT_DIR "${PROJECT_SOURCE_DIR}/binary") if(CMAKE_SYSTEM_NAME STREQUAL "Windows") string(APPEND OUTPUT_DIR "/windows") diff --git a/test/subscription_test.dart b/test/subscription_test.dart index dd970b68e..123f13d50 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -74,7 +74,7 @@ Future main([List? args]) async { expect(subscriptions.version, 2); }); - testSubscriptions('SubscriptionSet.add', (realm) { + testSubscriptions('MutableSubscriptionSet.add', (realm) { final subscriptions = realm.subscriptions; final query = realm.all(); @@ -85,7 +85,7 @@ Future main([List? args]) async { expect(subscriptions.find(query), isNotNull); }); - testSubscriptions('SubscriptionSet.add (named)', (realm) { + testSubscriptions('MutableSubscriptionSet.add (named)', (realm) { final subscriptions = realm.subscriptions; const name = 'some name'; @@ -120,7 +120,7 @@ Future main([List? args]) async { expect(subscriptions.findByName(name), isNotNull); }); - testSubscriptions('SubscriptionSet.remove', (realm) { + testSubscriptions('MutableSubscriptionSet.remove', (realm) { final subscriptions = realm.subscriptions; final query = realm.all(); @@ -135,7 +135,7 @@ Future main([List? args]) async { expect(subscriptions, isEmpty); }); - testSubscriptions('SubscriptionSet.remove (named)', (realm) { + testSubscriptions('MutableSubscriptionSet.remove (named)', (realm) { final subscriptions = realm.subscriptions; const name = 'some name'; @@ -150,7 +150,7 @@ Future main([List? args]) async { expect(subscriptions, isEmpty); }); - testSubscriptions('SubscriptionSet.removeAll', (realm) { + testSubscriptions('MutableSubscriptionSet.removeAll', (realm) { final subscriptions = realm.subscriptions; subscriptions.update((mutableSubscriptions) { @@ -201,21 +201,19 @@ Future main([List? args]) async { testSubscriptions('MutableSubscriptionSet.add double-add throws', (realm) { final subscriptions = realm.subscriptions; - // Cannot add same query twice without requesting an update - expect(() { - subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realm.all()); - mutableSubscriptions.add(realm.all()); - }); - }, throws('Duplicate subscription')); + // Adding same unnamed query twice without requesting an update will just de-duplicate + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(realm.all()); + }); + expect(subscriptions.length, 1); - // Okay to add same query under different names + // Okay to add same query under different names, not de-duplicated subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realm.all(), name: 'foo'); mutableSubscriptions.add(realm.all(), name: 'bar'); }); - - expect(subscriptions.length, 2); + expect(subscriptions.length, 3); // Cannot add different queries under same name, unless the second // can update the first. @@ -234,7 +232,7 @@ Future main([List? args]) async { mutableSubscriptions.add(realm.all()); mutableSubscriptions.add(realm.all(), update: true); }); - + expect(subscriptions.length, 1); subscriptions.update((mutableSubscriptions) { From f83850fcb13afebb3017ea4295d0de8814334652 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 5 May 2022 20:04:26 +0200 Subject: [PATCH 026/122] Update core to dc15bdc (v11.15.0-16-gdc15bdc36) --- lib/src/native/realm_bindings.dart | 346 +++++++++++++++++++++++++++-- lib/src/native/realm_core.dart | 6 +- src/realm-core | 2 +- 3 files changed, 328 insertions(+), 26 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index f3335353f..86e79f842 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -582,22 +582,28 @@ class RealmLibrary { ffi.Pointer arg0, realm_migration_func_t arg1, ffi.Pointer userdata, + realm_free_userdata_func_t callback, ) { return _realm_config_set_migration_function( arg0, arg1, userdata, + callback, ); } late final _realm_config_set_migration_functionPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, realm_migration_func_t, - ffi.Pointer)>>('realm_config_set_migration_function'); + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_migration_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>>( + 'realm_config_set_migration_function'); late final _realm_config_set_migration_function = _realm_config_set_migration_functionPtr.asFunction< void Function(ffi.Pointer, realm_migration_func_t, - ffi.Pointer)>(); + ffi.Pointer, realm_free_userdata_func_t)>(); /// Set the data initialization function. /// @@ -611,23 +617,31 @@ class RealmLibrary { ffi.Pointer arg0, realm_data_initialization_func_t arg1, ffi.Pointer userdata, + realm_free_userdata_func_t callback, ) { return _realm_config_set_data_initialization_function( arg0, arg1, userdata, + callback, ); } late final _realm_config_set_data_initialization_functionPtr = _lookup< ffi.NativeFunction< - ffi.Void Function(ffi.Pointer, - realm_data_initialization_func_t, ffi.Pointer)>>( + ffi.Void Function( + ffi.Pointer, + realm_data_initialization_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>>( 'realm_config_set_data_initialization_function'); late final _realm_config_set_data_initialization_function = _realm_config_set_data_initialization_functionPtr.asFunction< - void Function(ffi.Pointer, - realm_data_initialization_func_t, ffi.Pointer)>(); + void Function( + ffi.Pointer, + realm_data_initialization_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>(); /// Set the should-compact-on-launch callback. /// @@ -641,11 +655,13 @@ class RealmLibrary { ffi.Pointer arg0, realm_should_compact_on_launch_func_t arg1, ffi.Pointer userdata, + realm_free_userdata_func_t callback, ) { return _realm_config_set_should_compact_on_launch_function( arg0, arg1, userdata, + callback, ); } @@ -654,12 +670,16 @@ class RealmLibrary { ffi.Void Function( ffi.Pointer, realm_should_compact_on_launch_func_t, - ffi.Pointer)>>( + ffi.Pointer, + realm_free_userdata_func_t)>>( 'realm_config_set_should_compact_on_launch_function'); late final _realm_config_set_should_compact_on_launch_function = _realm_config_set_should_compact_on_launch_functionPtr.asFunction< - void Function(ffi.Pointer, - realm_should_compact_on_launch_func_t, ffi.Pointer)>(); + void Function( + ffi.Pointer, + realm_should_compact_on_launch_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>(); /// True if file format upgrades on open are disabled. /// @@ -6707,6 +6727,40 @@ class RealmLibrary { late final _realm_user_get_profile_data = _realm_user_get_profile_dataPtr .asFunction Function(ffi.Pointer)>(); + /// Return the access token associated with the user. + /// @return a string that rapresents the access token + ffi.Pointer realm_user_get_access_token( + ffi.Pointer arg0, + ) { + return _realm_user_get_access_token( + arg0, + ); + } + + late final _realm_user_get_access_tokenPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('realm_user_get_access_token'); + late final _realm_user_get_access_token = _realm_user_get_access_tokenPtr + .asFunction Function(ffi.Pointer)>(); + + /// Return the refresh token associated with the user. + /// @return a string that represents the refresh token + ffi.Pointer realm_user_get_refresh_token( + ffi.Pointer arg0, + ) { + return _realm_user_get_refresh_token( + arg0, + ); + } + + late final _realm_user_get_refresh_tokenPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer)>>('realm_user_get_refresh_token'); + late final _realm_user_get_refresh_token = _realm_user_get_refresh_tokenPtr + .asFunction Function(ffi.Pointer)>(); + ffi.Pointer realm_sync_client_config_new() { return _realm_sync_client_config_new(); } @@ -7244,6 +7298,174 @@ class RealmLibrary { _realm_sync_config_set_resync_modePtr .asFunction, int)>(); + void realm_sync_config_set_before_client_reset_handler( + ffi.Pointer arg0, + realm_sync_before_client_reset_func_t arg1, + ffi.Pointer userdata, + realm_free_userdata_func_t arg3, + ) { + return _realm_sync_config_set_before_client_reset_handler( + arg0, + arg1, + userdata, + arg3, + ); + } + + late final _realm_sync_config_set_before_client_reset_handlerPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_before_client_reset_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>>( + 'realm_sync_config_set_before_client_reset_handler'); + late final _realm_sync_config_set_before_client_reset_handler = + _realm_sync_config_set_before_client_reset_handlerPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_before_client_reset_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>(); + + void realm_sync_config_set_after_client_reset_handler( + ffi.Pointer arg0, + realm_sync_after_client_reset_func_t arg1, + ffi.Pointer userdata, + realm_free_userdata_func_t arg3, + ) { + return _realm_sync_config_set_after_client_reset_handler( + arg0, + arg1, + userdata, + arg3, + ); + } + + late final _realm_sync_config_set_after_client_reset_handlerPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_after_client_reset_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>>( + 'realm_sync_config_set_after_client_reset_handler'); + late final _realm_sync_config_set_after_client_reset_handler = + _realm_sync_config_set_after_client_reset_handlerPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_after_client_reset_func_t, + ffi.Pointer, + realm_free_userdata_func_t)>(); + + realm_object_id_t realm_flx_sync_subscription_id( + ffi.Pointer subscription, + ) { + return _realm_flx_sync_subscription_id( + subscription, + ); + } + + late final _realm_flx_sync_subscription_idPtr = _lookup< + ffi.NativeFunction< + realm_object_id_t Function( + ffi.Pointer)>>( + 'realm_flx_sync_subscription_id'); + late final _realm_flx_sync_subscription_id = + _realm_flx_sync_subscription_idPtr.asFunction< + realm_object_id_t Function( + ffi.Pointer)>(); + + realm_string_t realm_flx_sync_subscription_name( + ffi.Pointer subscription, + ) { + return _realm_flx_sync_subscription_name( + subscription, + ); + } + + late final _realm_flx_sync_subscription_namePtr = _lookup< + ffi.NativeFunction< + realm_string_t Function( + ffi.Pointer)>>( + 'realm_flx_sync_subscription_name'); + late final _realm_flx_sync_subscription_name = + _realm_flx_sync_subscription_namePtr.asFunction< + realm_string_t Function( + ffi.Pointer)>(); + + realm_string_t realm_flx_sync_subscription_object_class_name( + ffi.Pointer subscription, + ) { + return _realm_flx_sync_subscription_object_class_name( + subscription, + ); + } + + late final _realm_flx_sync_subscription_object_class_namePtr = _lookup< + ffi.NativeFunction< + realm_string_t Function( + ffi.Pointer)>>( + 'realm_flx_sync_subscription_object_class_name'); + late final _realm_flx_sync_subscription_object_class_name = + _realm_flx_sync_subscription_object_class_namePtr.asFunction< + realm_string_t Function( + ffi.Pointer)>(); + + realm_string_t realm_flx_sync_subscription_query_string( + ffi.Pointer subscription, + ) { + return _realm_flx_sync_subscription_query_string( + subscription, + ); + } + + late final _realm_flx_sync_subscription_query_stringPtr = _lookup< + ffi.NativeFunction< + realm_string_t Function( + ffi.Pointer)>>( + 'realm_flx_sync_subscription_query_string'); + late final _realm_flx_sync_subscription_query_string = + _realm_flx_sync_subscription_query_stringPtr.asFunction< + realm_string_t Function( + ffi.Pointer)>(); + + realm_timestamp_t realm_flx_sync_subscription_created_at( + ffi.Pointer subscription, + ) { + return _realm_flx_sync_subscription_created_at( + subscription, + ); + } + + late final _realm_flx_sync_subscription_created_atPtr = _lookup< + ffi.NativeFunction< + realm_timestamp_t Function( + ffi.Pointer)>>( + 'realm_flx_sync_subscription_created_at'); + late final _realm_flx_sync_subscription_created_at = + _realm_flx_sync_subscription_created_atPtr.asFunction< + realm_timestamp_t Function( + ffi.Pointer)>(); + + realm_timestamp_t realm_flx_sync_subscription_updated_at( + ffi.Pointer subscription, + ) { + return _realm_flx_sync_subscription_updated_at( + subscription, + ); + } + + late final _realm_flx_sync_subscription_updated_atPtr = _lookup< + ffi.NativeFunction< + realm_timestamp_t Function( + ffi.Pointer)>>( + 'realm_flx_sync_subscription_updated_at'); + late final _realm_flx_sync_subscription_updated_at = + _realm_flx_sync_subscription_updated_atPtr.asFunction< + realm_timestamp_t Function( + ffi.Pointer)>(); + /// Get latest subscription set /// @return a non null subscription set pointer if such it exists. ffi.Pointer @@ -7553,18 +7775,57 @@ class RealmLibrary { int Function( ffi.Pointer)>(); - /// Insert ot update a query for the subscription set passed as parameter, if successful the index where the query was - /// inserted or updated is returned along with the info whether a new query was inserted or not. It is possible to + /// Insert ot update the query contained inside a result object for the subscription set passed as parameter, if + /// successful the index where the query was inserted or updated is returned along with the info whether a new query + /// was inserted or not. It is possible to specify a name for the query inserted (optional). + /// @return true/false if operation was successful + bool realm_sync_subscription_set_insert_or_assign_results( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer name, + ffi.Pointer out_index, + ffi.Pointer out_inserted, + ) { + return _realm_sync_subscription_set_insert_or_assign_results( + arg0, + arg1, + name, + out_index, + out_inserted, + ) != + 0; + } + + late final _realm_sync_subscription_set_insert_or_assign_resultsPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>( + 'realm_sync_subscription_set_insert_or_assign_results'); + late final _realm_sync_subscription_set_insert_or_assign_results = + _realm_sync_subscription_set_insert_or_assign_resultsPtr.asFunction< + int Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>(); + + /// Insert ot update a query for the subscription set passed as parameter, if successful the index where the query + /// was inserted or updated is returned along with the info whether a new query was inserted or not. It is possible to /// specify a name for the query inserted (optional). /// @return true/false if operation was successful - bool realm_sync_subscription_set_insert_or_assign( + bool realm_sync_subscription_set_insert_or_assign_query( ffi.Pointer arg0, ffi.Pointer arg1, ffi.Pointer name, ffi.Pointer out_index, ffi.Pointer out_inserted, ) { - return _realm_sync_subscription_set_insert_or_assign( + return _realm_sync_subscription_set_insert_or_assign_query( arg0, arg1, name, @@ -7574,7 +7835,7 @@ class RealmLibrary { 0; } - late final _realm_sync_subscription_set_insert_or_assignPtr = _lookup< + late final _realm_sync_subscription_set_insert_or_assign_queryPtr = _lookup< ffi.NativeFunction< ffi.Uint8 Function( ffi.Pointer, @@ -7582,9 +7843,9 @@ class RealmLibrary { ffi.Pointer, ffi.Pointer, ffi.Pointer)>>( - 'realm_sync_subscription_set_insert_or_assign'); - late final _realm_sync_subscription_set_insert_or_assign = - _realm_sync_subscription_set_insert_or_assignPtr.asFunction< + 'realm_sync_subscription_set_insert_or_assign_query'); + late final _realm_sync_subscription_set_insert_or_assign_query = + _realm_sync_subscription_set_insert_or_assign_queryPtr.asFunction< int Function( ffi.Pointer, ffi.Pointer, @@ -7918,6 +8179,33 @@ class RealmLibrary { late final _realm_sync_session_resume = _realm_sync_session_resumePtr .asFunction)>(); + /// In case manual reset is needed, run this function in order to reset sync client files. + /// The sync_path is going to passed into realm_sync_error_handler_func_t, if manual reset is needed. + /// This function is supposed to be called inside realm_sync_error_handler_func_t callback, if sync client reset is + /// needed + /// @param realm_app ptr to realm app. + /// @param sync_path path where the sync files are. + /// @return true if operation was succesful + bool realm_sync_immediately_run_file_actions( + ffi.Pointer realm_app, + ffi.Pointer sync_path, + ) { + return _realm_sync_immediately_run_file_actions( + realm_app, + sync_path, + ) != + 0; + } + + late final _realm_sync_immediately_run_file_actionsPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function( + ffi.Pointer, ffi.Pointer)>>( + 'realm_sync_immediately_run_file_actions'); + late final _realm_sync_immediately_run_file_actions = + _realm_sync_immediately_run_file_actionsPtr.asFunction< + int Function(ffi.Pointer, ffi.Pointer)>(); + /// Register a callback that will be invoked every time the session's connection state changes. /// /// @return A token value that can be used to unregiser the callback. @@ -8687,6 +8975,8 @@ typedef realm_migration_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Uint8 Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, ffi.Pointer)>>; +typedef realm_free_userdata_func_t + = ffi.Pointer)>>; typedef realm_data_initialization_func_t = ffi.Pointer< ffi.NativeFunction< ffi.Uint8 Function(ffi.Pointer, ffi.Pointer)>>; @@ -8695,8 +8985,6 @@ typedef realm_should_compact_on_launch_func_t = ffi.Pointer< ffi.Uint8 Function(ffi.Pointer, ffi.Uint64, ffi.Uint64)>>; typedef realm_scheduler_t = realm_scheduler; typedef realm_sync_config_t = realm_sync_config; -typedef realm_free_userdata_func_t - = ffi.Pointer)>>; typedef realm_scheduler_notify_func_t = ffi.Pointer)>>; typedef realm_scheduler_is_on_thread_func_t = ffi @@ -9185,12 +9473,19 @@ class realm_sync_error extends ffi.Struct { external ffi.Pointer detailed_message; + external ffi.Pointer c_original_file_path_key; + + external ffi.Pointer c_recovery_file_path_key; + @ffi.Uint8() external int is_fatal; @ffi.Uint8() external int is_unrecognized_by_client; + @ffi.Uint8() + external int is_client_reset_requested; + external ffi.Pointer user_info_map; @ffi.IntPtr() @@ -9237,10 +9532,17 @@ typedef realm_sync_ssl_verify_func_t = ffi.Pointer< ffi.IntPtr, ffi.Int32, ffi.Int32)>>; +typedef realm_sync_before_client_reset_func_t = ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, ffi.Pointer)>>; +typedef realm_sync_after_client_reset_func_t = ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, ffi.Pointer, + ffi.Pointer, ffi.Uint8)>>; +typedef realm_flx_sync_subscription_t = realm_flx_sync_subscription; typedef realm_flx_sync_subscription_set_t = realm_flx_sync_subscription_set; typedef realm_sync_on_subscription_state_changed = ffi.Pointer< ffi.NativeFunction, ffi.Int32)>>; -typedef realm_flx_sync_subscription_t = realm_flx_sync_subscription; typedef realm_flx_sync_mutable_subscription_set_t = realm_flx_sync_mutable_subscription_set; typedef realm_async_open_task_t = realm_async_open_task; diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 5756e7fdc..869aa6748 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -163,7 +163,7 @@ class _RealmCore { if (config is LocalConfiguration) { if (config.initialDataCallback != null) { _realmLib.realm_config_set_data_initialization_function( - configHandle._pointer, Pointer.fromFunction(initial_data_callback, FALSE), config.toWeakHandle()); + configHandle._pointer, Pointer.fromFunction(initial_data_callback, FALSE), config.toWeakHandle(), nullptr); } if (config.isReadOnly) { _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); @@ -173,7 +173,7 @@ class _RealmCore { } if (config.shouldCompactCallback != null) { _realmLib.realm_config_set_should_compact_on_launch_function( - configHandle._pointer, Pointer.fromFunction(should_compact_callback, 0), config.toWeakHandle()); + configHandle._pointer, Pointer.fromFunction(should_compact_callback, 0), config.toWeakHandle(), nullptr); } } else if (config is InMemoryConfiguration) { _realmLib.realm_config_set_in_memory(configHandle._pointer, true); @@ -276,7 +276,7 @@ class _RealmCore { return using((arena) { final out_index = arena(); final out_inserted = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign( + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign_query( subscriptions.handle._mutablePointer, query.queryHandle._pointer, name?.toUtf8Ptr(arena) ?? nullptr, diff --git a/src/realm-core b/src/realm-core index c9c9f5e62..dc15bdc36 160000 --- a/src/realm-core +++ b/src/realm-core @@ -1 +1 @@ -Subproject commit c9c9f5e6228bbf8868587cdfe1feaae2d0666303 +Subproject commit dc15bdc364463f40a40715ef2fd768cca3e3f3ff From e7ed52f781c7545ba18c58cf46cb09bdc723a961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 6 May 2022 11:24:54 +0200 Subject: [PATCH 027/122] Wire up properties on Subscription --- lib/src/native/realm_core.dart | 49 ++++++++++++++++++++++++++++++++++ lib/src/subscription.dart | 28 +++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 869aa6748..5c1a46640 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -191,6 +191,36 @@ class _RealmCore { }); } + ObjectId subscriptionId(Subscription subscription) { + final id = _realmLib.realm_flx_sync_subscription_id(subscription.handle._pointer); + return id.toDart(); + } + + String? subscriptionName(Subscription subscription) { + final name = _realmLib.realm_flx_sync_subscription_name(subscription.handle._pointer); + return name.toDart(); + } + + String subscriptionObjectClassName(Subscription subscription) { + final objectClassName = _realmLib.realm_flx_sync_subscription_object_class_name(subscription.handle._pointer); + return objectClassName.toDart()!; + } + + String subscriptionQueryString(Subscription subscription) { + final queryString = _realmLib.realm_flx_sync_subscription_query_string(subscription.handle._pointer); + return queryString.toDart()!; + } + + DateTime subscriptionCreatedAt(Subscription subscription) { + final createdAt = _realmLib.realm_flx_sync_subscription_created_at(subscription.handle._pointer); + return createdAt.toDart(); + } + + DateTime subscriptionUpdatedAt(Subscription subscription) { + final updatedAt = _realmLib.realm_flx_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))); } @@ -1741,3 +1771,22 @@ enum _HttpMethod { put, delete, } + +extension on realm_object_id { + ObjectId toDart() { + return ObjectId(); // TODO + } +} + +extension on realm_timestamp_t { + DateTime toDart() { + return DateTime.fromMicrosecondsSinceEpoch(seconds * 1000000 + nanoseconds ~/ 1000, isUtc: true); + } +} + +extension on realm_string_t { + String? toDart() { + if (data == nullptr) return null; + return data.cast().toDartString(); + } +} diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 55fa87e0f..767eb772f 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -23,11 +23,39 @@ import 'native/realm_core.dart'; import 'realm_class.dart'; import 'util.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. class Subscription { final SubscriptionHandle _handle; Subscription._(this._handle); + ObjectId get id => realmCore.subscriptionId(this); + + /// Name of the [Subscription], if one was provided at creation time. + /// + /// Otherwise returns null. + String? get name => realmCore.subscriptionName(this); + + /// 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); + + /// 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); + + /// Timestamp when this [Subscription] was created. + DateTime get createdAt => realmCore.subscriptionCreatedAt(this); + + /// Timestamp when this [Subscription] was last updated. + DateTime get updatedAt => realmCore.subscriptionUpdatedAt(this); + @override // ignore: hash_and_equals bool operator ==(Object other) { From e34448e656bd0a75a4b80d9b37aee0ec547c2b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Fri, 6 May 2022 11:57:51 +0200 Subject: [PATCH 028/122] Add Subscription properties test --- lib/src/native/realm_core.dart | 26 ++++++++++++++++++++------ test/subscription_test.dart | 31 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 5c1a46640..f50e60f74 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1772,12 +1772,6 @@ enum _HttpMethod { delete, } -extension on realm_object_id { - ObjectId toDart() { - return ObjectId(); // TODO - } -} - extension on realm_timestamp_t { DateTime toDart() { return DateTime.fromMicrosecondsSinceEpoch(seconds * 1000000 + nanoseconds ~/ 1000, isUtc: true); @@ -1790,3 +1784,23 @@ extension on realm_string_t { return data.cast().toDartString(); } } + +extension on ObjectId { + Pointer toCapi(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); + } +} diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 123f13d50..02c217c56 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -278,4 +278,35 @@ Future main([List? args]) async { await subscriptions.waitForSynchronization(); }); + + testSubscriptions('Subscription properties roundtrip', (realm) async { + final subscriptions = realm.subscriptions; + + final before = DateTime.now().toUtc(); + + late ObjectId oid; + subscriptions.update((mutableSubscriptions) { + oid = mutableSubscriptions.add(realm.all(), name: 'foobar').id; + }); + + await subscriptions.waitForSynchronization(); // <-- Attempt at fixing windows + + final after = DateTime.now().toUtc(); + var s = subscriptions[0]; + + expect(s.id, oid); + expect(s.name, 'foobar'); + expect(s.objectClassName, 'Task'); + expect(s.queryString, 'TRUEPREDICATE'); + expect(s.createdAt.isAfter(before), isTrue); + expect(s.createdAt.isBefore(after), isTrue); + expect(s.createdAt, s.updatedAt); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.query(r'_id == $0', [ObjectId()]), name: 'foobar', update: true); + }); + + s = subscriptions[0]; // WARNING: Needed in order to refresh properties! + expect(s.createdAt.isBefore(s.updatedAt), isTrue); + }); } From dc695a409fb2faa8d0ce75be7c00acea5ca689bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 10 May 2022 17:04:44 +0200 Subject: [PATCH 029/122] Fix PR feedback --- lib/src/subscription.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 767eb772f..46354e1de 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -26,6 +26,7 @@ import 'util.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 { final SubscriptionHandle _handle; From 286c7bd203c829a1742bc9feee102ee26d7efbda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 15:25:02 +0200 Subject: [PATCH 030/122] Update core to 8c2ad6f (v11.15.0-24-g8c2ad6fc8) --- lib/src/native/realm_bindings.dart | 235 ++++++++++++++++++++--------- lib/src/native/realm_core.dart | 66 +++++--- src/realm-core | 2 +- 3 files changed, 209 insertions(+), 94 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 86e79f842..2d4defecb 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -7358,111 +7358,123 @@ class RealmLibrary { ffi.Pointer, realm_free_userdata_func_t)>(); - realm_object_id_t realm_flx_sync_subscription_id( + /// Fetch subscription id for the subscription passed as argument. + /// @return realm_object_id_t for the subscription passed as argument + realm_object_id_t realm_sync_subscription_id( ffi.Pointer subscription, ) { - return _realm_flx_sync_subscription_id( + return _realm_sync_subscription_id( subscription, ); } - late final _realm_flx_sync_subscription_idPtr = _lookup< + late final _realm_sync_subscription_idPtr = _lookup< ffi.NativeFunction< realm_object_id_t Function( ffi.Pointer)>>( - 'realm_flx_sync_subscription_id'); - late final _realm_flx_sync_subscription_id = - _realm_flx_sync_subscription_idPtr.asFunction< + 'realm_sync_subscription_id'); + late final _realm_sync_subscription_id = + _realm_sync_subscription_idPtr.asFunction< realm_object_id_t Function( ffi.Pointer)>(); - realm_string_t realm_flx_sync_subscription_name( + /// Fetch subscription name for the subscription passed as argument. + /// @return realm_string_t which contains the name of the subscription. + realm_string_t realm_sync_subscription_name( ffi.Pointer subscription, ) { - return _realm_flx_sync_subscription_name( + return _realm_sync_subscription_name( subscription, ); } - late final _realm_flx_sync_subscription_namePtr = _lookup< + late final _realm_sync_subscription_namePtr = _lookup< ffi.NativeFunction< realm_string_t Function( ffi.Pointer)>>( - 'realm_flx_sync_subscription_name'); - late final _realm_flx_sync_subscription_name = - _realm_flx_sync_subscription_namePtr.asFunction< + 'realm_sync_subscription_name'); + late final _realm_sync_subscription_name = + _realm_sync_subscription_namePtr.asFunction< realm_string_t Function( ffi.Pointer)>(); - realm_string_t realm_flx_sync_subscription_object_class_name( + /// Fetch object class name for the subscription passed as argument. + /// @return a realm_string_t which contains the class name of the subscription. + realm_string_t realm_sync_subscription_object_class_name( ffi.Pointer subscription, ) { - return _realm_flx_sync_subscription_object_class_name( + return _realm_sync_subscription_object_class_name( subscription, ); } - late final _realm_flx_sync_subscription_object_class_namePtr = _lookup< + late final _realm_sync_subscription_object_class_namePtr = _lookup< ffi.NativeFunction< realm_string_t Function( ffi.Pointer)>>( - 'realm_flx_sync_subscription_object_class_name'); - late final _realm_flx_sync_subscription_object_class_name = - _realm_flx_sync_subscription_object_class_namePtr.asFunction< + 'realm_sync_subscription_object_class_name'); + late final _realm_sync_subscription_object_class_name = + _realm_sync_subscription_object_class_namePtr.asFunction< realm_string_t Function( ffi.Pointer)>(); - realm_string_t realm_flx_sync_subscription_query_string( + /// Fetch the query string associated with the subscription passed as argument. + /// @return realm_string_t which contains the query associated with the subscription. + realm_string_t realm_sync_subscription_query_string( ffi.Pointer subscription, ) { - return _realm_flx_sync_subscription_query_string( + return _realm_sync_subscription_query_string( subscription, ); } - late final _realm_flx_sync_subscription_query_stringPtr = _lookup< + late final _realm_sync_subscription_query_stringPtr = _lookup< ffi.NativeFunction< realm_string_t Function( ffi.Pointer)>>( - 'realm_flx_sync_subscription_query_string'); - late final _realm_flx_sync_subscription_query_string = - _realm_flx_sync_subscription_query_stringPtr.asFunction< + 'realm_sync_subscription_query_string'); + late final _realm_sync_subscription_query_string = + _realm_sync_subscription_query_stringPtr.asFunction< realm_string_t Function( ffi.Pointer)>(); - realm_timestamp_t realm_flx_sync_subscription_created_at( + /// Fetch the timestamp in which the subscription was created for the subscription passed as argument. + /// @return realm_timestamp_t representing the timestamp in which the subscription for created. + realm_timestamp_t realm_sync_subscription_created_at( ffi.Pointer subscription, ) { - return _realm_flx_sync_subscription_created_at( + return _realm_sync_subscription_created_at( subscription, ); } - late final _realm_flx_sync_subscription_created_atPtr = _lookup< + late final _realm_sync_subscription_created_atPtr = _lookup< ffi.NativeFunction< realm_timestamp_t Function( ffi.Pointer)>>( - 'realm_flx_sync_subscription_created_at'); - late final _realm_flx_sync_subscription_created_at = - _realm_flx_sync_subscription_created_atPtr.asFunction< + 'realm_sync_subscription_created_at'); + late final _realm_sync_subscription_created_at = + _realm_sync_subscription_created_atPtr.asFunction< realm_timestamp_t Function( ffi.Pointer)>(); - realm_timestamp_t realm_flx_sync_subscription_updated_at( + /// Fetch the timestamp in which the subscription was updated for the subscription passed as argument. + /// @return realm_timestamp_t representing the timestamp in which the subscription was updated. + realm_timestamp_t realm_sync_subscription_updated_at( ffi.Pointer subscription, ) { - return _realm_flx_sync_subscription_updated_at( + return _realm_sync_subscription_updated_at( subscription, ); } - late final _realm_flx_sync_subscription_updated_atPtr = _lookup< + late final _realm_sync_subscription_updated_atPtr = _lookup< ffi.NativeFunction< realm_timestamp_t Function( ffi.Pointer)>>( - 'realm_flx_sync_subscription_updated_at'); - late final _realm_flx_sync_subscription_updated_at = - _realm_flx_sync_subscription_updated_atPtr.asFunction< + 'realm_sync_subscription_updated_at'); + late final _realm_sync_subscription_updated_at = + _realm_sync_subscription_updated_atPtr.asFunction< realm_timestamp_t Function( ffi.Pointer)>(); @@ -7664,30 +7676,6 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer, int)>(); - /// Find subscription by name - /// @return a pointer to the subscription with the name passed as parameter - ffi.Pointer - realm_sync_find_subscription_by_name( - ffi.Pointer arg0, - ffi.Pointer name, - ) { - return _realm_sync_find_subscription_by_name( - arg0, - name, - ); - } - - late final _realm_sync_find_subscription_by_namePtr = _lookup< - ffi.NativeFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer)>>('realm_sync_find_subscription_by_name'); - late final _realm_sync_find_subscription_by_name = - _realm_sync_find_subscription_by_namePtr.asFunction< - ffi.Pointer Function( - ffi.Pointer, - ffi.Pointer)>(); - /// Find subscription associated to the query passed as parameter /// @return a pointer to the subscription or nullptr if not found ffi.Pointer @@ -7713,6 +7701,55 @@ class RealmLibrary { ffi.Pointer, ffi.Pointer)>(); + /// Find subscription associated to the results set passed as parameter + /// @return a pointer to the subscription or nullptr if not found + ffi.Pointer + realm_sync_find_subscription_by_results( + ffi.Pointer arg0, + ffi.Pointer arg1, + ) { + return _realm_sync_find_subscription_by_results( + arg0, + arg1, + ); + } + + late final _realm_sync_find_subscription_by_resultsPtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer)>>( + 'realm_sync_find_subscription_by_results'); + late final _realm_sync_find_subscription_by_results = + _realm_sync_find_subscription_by_resultsPtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer)>(); + + /// Find subscription by name passed as parameter + /// @return a pointer to the subscription or nullptr if not found + ffi.Pointer + realm_sync_find_subscription_by_name( + ffi.Pointer arg0, + ffi.Pointer name, + ) { + return _realm_sync_find_subscription_by_name( + arg0, + name, + ); + } + + late final _realm_sync_find_subscription_by_namePtr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer)>>('realm_sync_find_subscription_by_name'); + late final _realm_sync_find_subscription_by_name = + _realm_sync_find_subscription_by_namePtr.asFunction< + ffi.Pointer Function( + ffi.Pointer, + ffi.Pointer)>(); + /// Refresh subscription /// @return true/false if the operation was successful or not bool realm_sync_subscription_set_refresh( @@ -7853,15 +7890,44 @@ class RealmLibrary { ffi.Pointer, ffi.Pointer)>(); - /// Erase from subscription set by name - /// @return true/false if operation was successful + /// Erase from subscription set by id. If operation completes successfully set the bool out param. + /// @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error). + bool realm_sync_subscription_set_erase_by_id( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer arg2, + ) { + return _realm_sync_subscription_set_erase_by_id( + arg0, + arg1, + arg2, + ) != + 0; + } + + late final _realm_sync_subscription_set_erase_by_idPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>( + 'realm_sync_subscription_set_erase_by_id'); + late final _realm_sync_subscription_set_erase_by_id = + _realm_sync_subscription_set_erase_by_idPtr.asFunction< + int Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>(); + + /// Erase from subscription set by name. If operation completes successfully set the bool out param. + /// @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error) bool realm_sync_subscription_set_erase_by_name( ffi.Pointer arg0, ffi.Pointer arg1, + ffi.Pointer erased, ) { return _realm_sync_subscription_set_erase_by_name( arg0, arg1, + erased, ) != 0; } @@ -7870,22 +7936,25 @@ class RealmLibrary { ffi.NativeFunction< ffi.Uint8 Function( ffi.Pointer, - ffi.Pointer)>>( + ffi.Pointer, + ffi.Pointer)>>( 'realm_sync_subscription_set_erase_by_name'); late final _realm_sync_subscription_set_erase_by_name = _realm_sync_subscription_set_erase_by_namePtr.asFunction< int Function(ffi.Pointer, - ffi.Pointer)>(); + ffi.Pointer, ffi.Pointer)>(); - /// Erase from subscription set by query - /// @return true/false if operation was successful + /// Erase from subscription set by query. If operation completes successfully set the bool out param. + /// @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error) bool realm_sync_subscription_set_erase_by_query( ffi.Pointer arg0, ffi.Pointer arg1, + ffi.Pointer erased, ) { return _realm_sync_subscription_set_erase_by_query( arg0, arg1, + erased, ) != 0; } @@ -7894,12 +7963,40 @@ class RealmLibrary { ffi.NativeFunction< ffi.Uint8 Function( ffi.Pointer, - ffi.Pointer)>>( + ffi.Pointer, + ffi.Pointer)>>( 'realm_sync_subscription_set_erase_by_query'); late final _realm_sync_subscription_set_erase_by_query = _realm_sync_subscription_set_erase_by_queryPtr.asFunction< int Function(ffi.Pointer, - ffi.Pointer)>(); + ffi.Pointer, ffi.Pointer)>(); + + /// Erase from subscription set by results. If operation completes successfully set the bool out param. + /// @return true if no error occurred, false otherwise (use realm_get_last_error for fetching the error) + bool realm_sync_subscription_set_erase_by_results( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.Pointer erased, + ) { + return _realm_sync_subscription_set_erase_by_results( + arg0, + arg1, + erased, + ) != + 0; + } + + late final _realm_sync_subscription_set_erase_by_resultsPtr = _lookup< + ffi.NativeFunction< + ffi.Uint8 Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>( + 'realm_sync_subscription_set_erase_by_results'); + late final _realm_sync_subscription_set_erase_by_results = + _realm_sync_subscription_set_erase_by_resultsPtr.asFunction< + int Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer)>(); /// Commit the subscription_set passed as parameter (in order that all the changes made will take effect) /// @return pointer to a valid immutable subscription if commit was successful @@ -9554,6 +9651,8 @@ typedef realm_async_open_task_t = realm_async_open_task; /// @param realm Downloaded realm instance, or null if an error occurred. /// Move to the thread you want to use it on and /// thaw with @a realm_from_thread_safe_reference(). +/// Be aware that once received through this call, you own +/// the object and must release it when used. /// @param error Null, if the operation complete successfully. typedef realm_async_open_task_completion_func_t = ffi.Pointer< ffi.NativeFunction< diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index f50e60f74..61f46e7d0 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -163,7 +163,11 @@ class _RealmCore { if (config is LocalConfiguration) { if (config.initialDataCallback != null) { _realmLib.realm_config_set_data_initialization_function( - configHandle._pointer, Pointer.fromFunction(initial_data_callback, FALSE), config.toWeakHandle(), nullptr); + configHandle._pointer, + Pointer.fromFunction(initial_data_callback, FALSE), + config.toWeakHandle(), + nullptr, + ); } if (config.isReadOnly) { _realmLib.realm_config_set_schema_mode(configHandle._pointer, realm_schema_mode.RLM_SCHEMA_MODE_IMMUTABLE); @@ -173,7 +177,11 @@ class _RealmCore { } if (config.shouldCompactCallback != null) { _realmLib.realm_config_set_should_compact_on_launch_function( - configHandle._pointer, Pointer.fromFunction(should_compact_callback, 0), config.toWeakHandle(), nullptr); + configHandle._pointer, + Pointer.fromFunction(should_compact_callback, 0), + config.toWeakHandle(), + nullptr, + ); } } else if (config is InMemoryConfiguration) { _realmLib.realm_config_set_in_memory(configHandle._pointer, true); @@ -192,32 +200,32 @@ class _RealmCore { } ObjectId subscriptionId(Subscription subscription) { - final id = _realmLib.realm_flx_sync_subscription_id(subscription.handle._pointer); + final id = _realmLib.realm_sync_subscription_id(subscription.handle._pointer); return id.toDart(); } String? subscriptionName(Subscription subscription) { - final name = _realmLib.realm_flx_sync_subscription_name(subscription.handle._pointer); + final name = _realmLib.realm_sync_subscription_name(subscription.handle._pointer); return name.toDart(); } String subscriptionObjectClassName(Subscription subscription) { - final objectClassName = _realmLib.realm_flx_sync_subscription_object_class_name(subscription.handle._pointer); + final objectClassName = _realmLib.realm_sync_subscription_object_class_name(subscription.handle._pointer); return objectClassName.toDart()!; } String subscriptionQueryString(Subscription subscription) { - final queryString = _realmLib.realm_flx_sync_subscription_query_string(subscription.handle._pointer); + final queryString = _realmLib.realm_sync_subscription_query_string(subscription.handle._pointer); return queryString.toDart()!; } DateTime subscriptionCreatedAt(Subscription subscription) { - final createdAt = _realmLib.realm_flx_sync_subscription_created_at(subscription.handle._pointer); + final createdAt = _realmLib.realm_sync_subscription_created_at(subscription.handle._pointer); return createdAt.toDart(); } DateTime subscriptionUpdatedAt(Subscription subscription) { - final updatedAt = _realmLib.realm_flx_sync_subscription_updated_at(subscription.handle._pointer); + final updatedAt = _realmLib.realm_sync_subscription_updated_at(subscription.handle._pointer); return updatedAt.toDart(); } @@ -322,31 +330,39 @@ class _RealmCore { } bool eraseSubscription(MutableSubscriptionSet subscriptions, Subscription subscription) { - // TODO: Awaiting fix for https://github.com/realm/realm-core/issues/5475 - // Should look something like: - /* - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_subscription( - subscriptions.handle._mutablePointer, - subscription.handle._pointer, - )); - */ - return false; // TEMPORARY!! + return using((arena) { + final out_found = arena.allocate(1); + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_id( + subscriptions.handle._mutablePointer, + subscription.id.toCapi(arena), + out_found, + )); + return out_found.value != 0; + }); } bool eraseSubscriptionByName(MutableSubscriptionSet subscriptions, String name) { return using((arena) { - return _realmLib.realm_sync_subscription_set_erase_by_name( - subscriptions.handle._mutablePointer, - name.toUtf8Ptr(arena), - ); + final out_found = arena.allocate(1); + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_name( + subscriptions.handle._mutablePointer, + name.toUtf8Ptr(arena), + out_found, + )); + return out_found.value != 0; }); } bool eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { - return _realmLib.realm_sync_subscription_set_erase_by_query( - subscriptions.handle._mutablePointer, - query.queryHandle._pointer, - ); + return using((arena) { + final out_found = arena.allocate(1); + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_query( + subscriptions.handle._mutablePointer, + query.queryHandle._pointer, + out_found, + )); + return out_found.value != 0; + }); } void clearSubscriptionSet(MutableSubscriptionSet subscriptions) { diff --git a/src/realm-core b/src/realm-core index dc15bdc36..8c2ad6fc8 160000 --- a/src/realm-core +++ b/src/realm-core @@ -1 +1 @@ -Subproject commit dc15bdc364463f40a40715ef2fd768cca3e3f3ff +Subproject commit 8c2ad6fc800417784acbe238d41b8409ee3fe991 From fc9c426aa13b582f3dd65203bf6a955c05c847be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 17:23:18 +0200 Subject: [PATCH 031/122] Avoid storing _queryHandle on RealmResult This is possible given recent changes to the C-API. --- lib/src/list.dart | 3 ++- lib/src/native/realm_core.dart | 20 ++++++++------------ lib/src/realm_class.dart | 7 ++----- lib/src/results.dart | 16 ++++++---------- lib/src/subscription.dart | 10 +++++----- test/subscription_test.dart | 21 +++++++++++++++++++-- 6 files changed, 42 insertions(+), 35 deletions(-) diff --git a/lib/src/list.dart b/lib/src/list.dart index 7d3f70eb7..698666322 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -131,7 +131,8 @@ extension RealmListOfObject on RealmList { /// 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 arguments = const []]) { final managedList = asManaged(); - final handle = realmCore.queryList(managedList, query, arguments); + final queryHandle = realmCore.queryList(managedList, query, arguments); + final handle = realmCore.queryFindAll(queryHandle); return RealmResultsInternal.create(handle, realm); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 61f46e7d0..00e5fefb2 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -261,9 +261,9 @@ class _RealmCore { SubscriptionHandle? findSubscriptionByQuery(SubscriptionSet subscriptions, RealmResults query) { return _realmLib - .realm_sync_find_subscription_by_query( + .realm_sync_find_subscription_by_results( subscriptions.handle._pointer, - query.queryHandle._pointer, + query.handle._pointer, ) .convert(SubscriptionHandle._); } @@ -314,9 +314,9 @@ class _RealmCore { return using((arena) { final out_index = arena(); final out_inserted = arena(); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign_query( + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_insert_or_assign_results( subscriptions.handle._mutablePointer, - query.queryHandle._pointer, + query.handle._pointer, name?.toUtf8Ptr(arena) ?? nullptr, out_index, out_inserted, @@ -325,11 +325,7 @@ class _RealmCore { }); } - String describeQuery(RealmResults query) { - return _realmLib.realm_query_get_description(query.queryHandle._pointer).cast().toDartString(); - } - - bool eraseSubscription(MutableSubscriptionSet subscriptions, Subscription subscription) { + bool eraseSubscriptionById(MutableSubscriptionSet subscriptions, Subscription subscription) { return using((arena) { final out_found = arena.allocate(1); _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_id( @@ -353,12 +349,12 @@ class _RealmCore { }); } - bool eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults query) { + bool eraseSubscriptionByQuery(MutableSubscriptionSet subscriptions, RealmResults results) { return using((arena) { final out_found = arena.allocate(1); - _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_query( + _realmLib.invokeGetBool(() => _realmLib.realm_sync_subscription_set_erase_by_results( subscriptions.handle._mutablePointer, - query.queryHandle._pointer, + results.handle._pointer, out_found, )); return out_found.value != 0; diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index eedd3d97d..cfb23f834 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -255,13 +255,9 @@ class Realm { /// /// The returned [RealmResults] allows iterating all the values without further filtering. RealmResults all() { - return query('TRUEPREDICATE'); - // TODO: The below is more efficient, but doesn't expose the query. We should fix the C-API. - /* RealmMetadata metadata = _getMetadata(T); final handle = realmCore.findAll(this, metadata.class_.key); return RealmResultsInternal.create(handle, this); - */ } /// Returns all [RealmObject]s that match the specified [query]. @@ -271,7 +267,8 @@ class Realm { RealmResults query(String query, [List args = const []]) { RealmMetadata metadata = _getMetadata(T); final queryHandle = realmCore.queryClass(this, metadata.class_.key, query, args); - return RealmResultsInternal.create(queryHandle, this); + final handle = realmCore.queryFindAll(queryHandle); + return RealmResultsInternal.create(handle, this); } /// Deletes all [RealmObject]s of type `T` in the `Realm` diff --git a/lib/src/results.dart b/lib/src/results.dart index 30225f620..cf80d6c94 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -28,7 +28,6 @@ import 'realm_class.dart'; /// /// {@category Realm} class RealmResults extends collection.IterableBase { - final RealmQueryHandle _queryHandle; final RealmResultsHandle _handle; /// The Realm instance this collection belongs to. @@ -36,7 +35,7 @@ class RealmResults extends collection.IterableBase { final _supportsSnapshot = [] is List; - RealmResults._(this._queryHandle, this._handle, this.realm); + RealmResults._(this._handle, this.realm); /// Returns the element of type `T` at the specified [index]. T operator [](int index) { @@ -50,7 +49,8 @@ class RealmResults extends collection.IterableBase { /// 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 queryHandle = realmCore.queryResults(this, query, args); - return RealmResultsInternal.create(queryHandle, realm); + final handle = realmCore.queryFindAll(queryHandle); + return RealmResultsInternal.create(handle, realm); } /// `true` if the `Results` collection is empty. @@ -63,7 +63,7 @@ class RealmResults extends collection.IterableBase { var results = this; if (_supportsSnapshot) { final handle = realmCore.resultsSnapshot(this); - results = RealmResults._(_queryHandle, handle, realm); + results = RealmResults._(handle, realm); } return _RealmResultsIterator(results); } @@ -77,19 +77,15 @@ class RealmResults extends collection.IterableBase { final controller = ResultsNotificationsController(this); return controller.createStream(); } - - @override - String toString() => realmCore.describeQuery(this); } /// @nodoc //RealmResults package internal members extension RealmResultsInternal on RealmResults { - RealmQueryHandle get queryHandle => _queryHandle; RealmResultsHandle get handle => _handle; - static RealmResults create(RealmQueryHandle queryHandle, Realm realm) { - return RealmResults._(queryHandle, realmCore.queryFindAll(queryHandle), realm); + static RealmResults create(RealmResultsHandle handle, Realm realm) { + return RealmResults._(handle, realm); } } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 46354e1de..ee9781887 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -246,17 +246,17 @@ class MutableSubscriptionSet extends SubscriptionSet { return Subscription._(realmCore.insertOrAssignSubscription(this, query, name, update)); } - // TODO: Make this public when C-API is in place (see: https://github.com/realm/realm-core/issues/5475) - bool _remove(Subscription subscription) { - return realmCore.eraseSubscription(this, subscription); + /// Remove the [subscription] from the set, if it exists. + bool remove(Subscription subscription) { + return realmCore.eraseSubscriptionById(this, subscription); } - /// Remove the [query] from the set that matches, if any. + /// Remove the [query] from the set, if it exists. bool removeByQuery(RealmResults query) { return realmCore.eraseSubscriptionByQuery(this, query); } - /// Remove the [query] from the set that matches by [name], if any + /// Remove the [query] from the set that matches by [name], if it exists. bool removeByName(String name) { return realmCore.eraseSubscriptionByName(this, name); } diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 02c217c56..e57dfc251 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -108,7 +108,7 @@ Future main([List? args]) async { expect(subscriptions.find(query), isNotNull); }); - testSubscriptions('SubscriptionSet.find (named)', (realm) { + testSubscriptions('SubscriptionSet.findByName', (realm) { final subscriptions = realm.subscriptions; const name = 'some name'; @@ -129,13 +129,30 @@ Future main([List? args]) async { }); expect(subscriptions, isNotEmpty); + final s = subscriptions[0]; + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.remove(s); + }); + expect(subscriptions, isEmpty); + }); + + testSubscriptions('MutableSubscriptionSet.removeByQuery', (realm) { + final subscriptions = realm.subscriptions; + final query = realm.all(); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(query); + }); + expect(subscriptions, isNotEmpty); + subscriptions.update((mutableSubscriptions) { mutableSubscriptions.removeByQuery(query); }); expect(subscriptions, isEmpty); }); - testSubscriptions('MutableSubscriptionSet.remove (named)', (realm) { + testSubscriptions('MutableSubscriptionSet.removeByName', (realm) { final subscriptions = realm.subscriptions; const name = 'some name'; From 5dcb73d85ddf9bbb2e7500e9c2b8c5b85d9fc05e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 10:30:14 +0200 Subject: [PATCH 032/122] Added sync roundtrip test Using delay instead of awaiting sync session upload/download, since SyncSession is not yet implemented. --- test/subscription_test.dart | 46 +++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index e57dfc251..e0ca81e82 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -15,15 +15,16 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// - import 'dart:async'; import 'dart:io'; import 'package:meta/meta.dart'; +import 'package:path/path.dart' as path; import 'package:test/expect.dart'; import '../lib/realm.dart'; import '../lib/src/configuration.dart'; +import '../lib/src/native/realm_core.dart'; import 'test.dart'; @isTest @@ -323,7 +324,48 @@ Future main([List? args]) async { mutableSubscriptions.add(realm.query(r'_id == $0', [ObjectId()]), name: 'foobar', update: true); }); - s = subscriptions[0]; // WARNING: Needed in order to refresh properties! + s = subscriptions[0]; // WARNING: Needed in order to refresh properties! expect(s.createdAt.isBefore(s.updatedAt), isTrue); }); + + baasTest('flexible sync roundtrip', (appConfigurationX) async { + final appX = App(appConfigurationX); + + realmCore.clearCachedApps(); + final temporaryDir = await Directory.systemTemp.createTemp('realm_test_Y_'); + final appConfigurationY = AppConfiguration( + appConfigurationX.appId, + baseUrl: appConfigurationX.baseUrl, + baseFilePath: temporaryDir, + ); + final appY = App(appConfigurationY); + + final credentials = Credentials.anonymous(); + final userX = await appX.logIn(credentials); + final userY = await appY.logIn(credentials); + + final realmX = getRealm(Configuration.flexibleSync(userX, [Task.schema])); + final pathY = path.join(temporaryDir.path, "Y.realm"); + final realmY = getRealm(Configuration.flexibleSync(userY, [Task.schema], path: pathY)); // TODO: Why do I need to set path here? + + realmX.subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realmX.all()); + }); + + final oid = ObjectId(); + realmX.write(() => realmX.add(Task(oid))); + + realmY.subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realmY.all()); + }); + + await realmX.subscriptions.waitForSynchronization(); + await realmY.subscriptions.waitForSynchronization(); + + // TODO: Missing sync session wait methods here, so just add big timeout for now + await Future.delayed(const Duration(seconds: 2)); + + final t = realmY.find(oid); + expect(t, isNotNull); + }); } From 6fe33814c9dc471be8c776a9f419eb1e4c5a725e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 23:18:47 +0200 Subject: [PATCH 033/122] Hide SubscriptionSetState uncommitted and bootstrapping from users --- lib/src/subscription.dart | 15 ++++++++++++--- test/subscription_test.dart | 6 +++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index ee9781887..c1ef628d5 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -87,13 +87,13 @@ class _SubscriptionIterator implements Iterator { enum SubscriptionSetState { /// This subscription set has not been persisted and has not been sent to the server. /// This state is only valid for MutableSubscriptionSets - uncommitted, + _uncommitted, // ignore: unused_field /// The subscription set has been persisted locally but has not been acknowledged by the server yet. pending, /// The server is currently sending the initial state that represents this subscription set to the client. - bootstrapping, + _bootstrapping, // ignore: unused_field /// This subscription set is the active subscription set that is currently being synchronized with the server. complete, @@ -194,7 +194,16 @@ abstract class SubscriptionSet with IterableMixin { int get version => realmCore.subscriptionSetGetVersion(this); /// Gets the state of the subscription set. - SubscriptionSetState get state => realmCore.subscriptionSetGetState(this); + SubscriptionSetState get state { + final state = realmCore.subscriptionSetGetState(this); + switch (state) { + case SubscriptionSetState._uncommitted: + case SubscriptionSetState._bootstrapping: + return SubscriptionSetState.pending; + default: + return state; + } + } } extension SubscriptionSetInternal on SubscriptionSet { diff --git a/test/subscription_test.dart b/test/subscription_test.dart index e0ca81e82..b67a0e892 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -196,7 +196,7 @@ Future main([List? args]) async { for (final s in subscriptions) { expect(s, s); expect(subscriptions[index], isNotNull); - /* TODO: Not posible yet + /* TODO: Not posible yet, due to C-API expect(subscriptions[index], subscriptions[index]); expect(s, subscriptions[index]); */ @@ -211,8 +211,8 @@ Future main([List? args]) async { final s = mutableSubscriptions.add(realm.all()); expect(mutableSubscriptions[0], isNotNull); expect(s, isNotNull); - expect(mutableSubscriptions.state, SubscriptionSetState.uncommitted); - // expect(mutableSubscriptions[0], s); // TODO: Not posible yet + expect(mutableSubscriptions.state, SubscriptionSetState.pending); // not _uncommitted! + // expect(mutableSubscriptions[0], s); // TODO: Not posible yet, due to C-API }); }); From f044a2a6c976713133f93c01eb3f8b3d5328d7ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 00:32:06 +0200 Subject: [PATCH 034/122] Tweak Subscription.operator== to use .id (workaround for capi issue) --- lib/src/subscription.dart | 4 +++- test/subscription_test.dart | 37 +++++++++++++++++++++++++++++++++---- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index c1ef628d5..bbe46da91 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -62,7 +62,9 @@ class Subscription { bool operator ==(Object other) { if (identical(this, other)) return true; if (other is! Subscription) return false; - return realmCore.subscriptionEquals(this, other); + // TODO: Don't work, issue with C-API + // return realmCore.subscriptionEquals(this, other); + return id == other.id; // <-- do this instead } } diff --git a/test/subscription_test.dart b/test/subscription_test.dart index b67a0e892..26a0b1445 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -17,6 +17,8 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:async'; import 'dart:io'; +import 'dart:math'; +import 'dart:typed_data'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as path; @@ -196,10 +198,8 @@ Future main([List? args]) async { for (final s in subscriptions) { expect(s, s); expect(subscriptions[index], isNotNull); - /* TODO: Not posible yet, due to C-API expect(subscriptions[index], subscriptions[index]); expect(s, subscriptions[index]); - */ ++index; } }); @@ -212,7 +212,7 @@ Future main([List? args]) async { expect(mutableSubscriptions[0], isNotNull); expect(s, isNotNull); expect(mutableSubscriptions.state, SubscriptionSetState.pending); // not _uncommitted! - // expect(mutableSubscriptions[0], s); // TODO: Not posible yet, due to C-API + expect(mutableSubscriptions[0], s); }); }); @@ -261,6 +261,35 @@ Future main([List? args]) async { expect(subscriptions.length, 2); }); + testSubscriptions('MutableSubscriptionSet.add multiple queries for same class', (realm) { + final subscriptions = realm.subscriptions; + final r = Random.secure(); + + Uint8List randomBytes(int n) { + final Uint8List random = Uint8List(n); + for (int i = 0; i < random.length; i++) { + random[i] = r.nextInt(255); + } + return random; + } + + ObjectId newOid() => ObjectId.fromBytes(randomBytes(12)); + + final oids = {}; + const max = 1000; + subscriptions.update((mutableSubscriptions) { + oids.addAll([ + for (int i = 0; i < max; ++i) mutableSubscriptions.add(realm.query(r'_id == $0', [newOid()])).id + ]); + }); + expect(oids.length, max); // no collisions + expect(subscriptions.length, max); + + for (final s in subscriptions) { + expect(s.id, isIn(oids)); + } + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; @@ -330,7 +359,7 @@ Future main([List? args]) async { baasTest('flexible sync roundtrip', (appConfigurationX) async { final appX = App(appConfigurationX); - + realmCore.clearCachedApps(); final temporaryDir = await Directory.systemTemp.createTemp('realm_test_Y_'); final appConfigurationY = AppConfiguration( From cc7985a7cea833acf787485ea32f2293680c6155 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 00:44:54 +0200 Subject: [PATCH 035/122] Test subscriptions with same name, but different classes throws --- test/subscription_test.dart | 14 ++++++++++++- test/test.dart | 9 +++++++++ test/test.g.dart | 40 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 26a0b1445..f69dccc0a 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -35,7 +35,7 @@ void testSubscriptions(String name, FutureOr Function(Realm) tester) async final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = FlexibleSyncConfiguration(user, [Task.schema])..sessionStopPolicy = SessionStopPolicy.immediately; + final configuration = FlexibleSyncConfiguration(user, [Task.schema, Schedule.schema])..sessionStopPolicy = SessionStopPolicy.immediately; final realm = getRealm(configuration); await tester(realm); }); @@ -290,6 +290,18 @@ Future main([List? args]) async { } }); + //multiple named with same name but different classes should throw. + testSubscriptions('MutableSubscriptionSet.add same name, different classes', (realm) { + final subscriptions = realm.subscriptions; + + expect( + () => subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all(), name: 'same'); + mutableSubscriptions.add(realm.all(), name: 'same'); + }), + throws()); + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; diff --git a/test/test.dart b/test/test.dart index 577dfe3ad..f8676c228 100644 --- a/test/test.dart +++ b/test/test.dart @@ -94,6 +94,15 @@ class _Task { late ObjectId id; } +@RealmModel() +class _Schedule { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; + final tasks = <_Task>[]; +} + + String? testName; final baasApps = {}; final _openRealms = Queue(); diff --git a/test/test.g.dart b/test/test.g.dart index 1919a2aaa..4033dffc9 100644 --- a/test/test.g.dart +++ b/test/test.g.dart @@ -356,3 +356,43 @@ class Task extends _Task with RealmEntity, RealmObject { ]); } } + +class Schedule extends _Schedule with RealmEntity, RealmObject { + Schedule( + ObjectId id, { + Iterable tasks = const [], + }) { + RealmObject.set(this, '_id', id); + RealmObject.set>(this, 'tasks', RealmList(tasks)); + } + + Schedule._(); + + @override + ObjectId get id => RealmObject.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => throw RealmUnsupportedSetError(); + + @override + RealmList get tasks => + RealmObject.get(this, 'tasks') as RealmList; + @override + set tasks(covariant RealmList value) => + throw RealmUnsupportedSetError(); + + @override + Stream> get changes => + RealmObject.getChanges(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(Schedule._); + return const SchemaObject(Schedule, 'Schedule', [ + SchemaProperty('_id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + SchemaProperty('tasks', RealmPropertyType.object, + linkTarget: 'Task', collectionType: RealmCollectionType.list), + ]); + } +} From 79f563f7c03ed5b8986eeb97518cdede14bb833d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 00:49:58 +0200 Subject: [PATCH 036/122] Test adding subscriptions with same name, different classes, with update flag --- test/subscription_test.dart | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index f69dccc0a..72139105c 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -290,7 +290,6 @@ Future main([List? args]) async { } }); - //multiple named with same name but different classes should throw. testSubscriptions('MutableSubscriptionSet.add same name, different classes', (realm) { final subscriptions = realm.subscriptions; @@ -302,6 +301,19 @@ Future main([List? args]) async { throws()); }); + testSubscriptions('MutableSubscriptionSet.add same name, different classes, with update flag', (realm) { + final subscriptions = realm.subscriptions; + + late Subscription s; + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all(), name: 'same'); + s = mutableSubscriptions.add(realm.all(), name: 'same', update: true); + }); + + expect(subscriptions.length, 1); + expect(subscriptions[0], s); // last added wins + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; From 4fce4778e2ec77b56417a664c280ba2846740b33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 00:51:53 +0200 Subject: [PATCH 037/122] Test adding subscriptions with same query, different classes --- test/subscription_test.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 72139105c..f01377288 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -314,6 +314,20 @@ Future main([List? args]) async { expect(subscriptions[0], s); // last added wins }); + testSubscriptions('MutableSubscriptionSet.add same query, different classes', (realm) { + final subscriptions = realm.subscriptions; + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all()); + mutableSubscriptions.add(realm.all()); + }); + + expect(subscriptions.length, 2); + for (final s in subscriptions) { + expect(s.queryString, 'TRUEPREDICATE'); + } + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; From b02003736eb0b326b4f0755edbc934ea6750aeca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 01:00:47 +0200 Subject: [PATCH 038/122] Test SubscriptionSet.find return match, even if named --- test/subscription_test.dart | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index f01377288..d716574b3 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -52,7 +52,7 @@ Future main([List? args]) async { expect(() => realm.subscriptions, throws()); }); - testSubscriptions('SubscriptionSet.state', (realm) async { + testSubscriptions('SubscriptionSet.state/waitForSynchronization', (realm) async { final subscriptions = realm.subscriptions; await subscriptions.waitForSynchronization(); expect(subscriptions.state, SubscriptionSetState.complete); @@ -110,6 +110,19 @@ Future main([List? args]) async { }); expect(subscriptions.find(query), isNotNull); }); + + testSubscriptions('SubscriptionSet.find return match, even if named', (realm) { + final subscriptions = realm.subscriptions; + final query = realm.all(); + + expect(subscriptions.find(query), isNull); + + late Subscription s; + subscriptions.update((mutableSubscriptions) { + s = mutableSubscriptions.add(query, name: 'foobar'); + }); + expect(subscriptions.find(query), s); + }); testSubscriptions('SubscriptionSet.findByName', (realm) { final subscriptions = realm.subscriptions; From 828e5180a198ead8768c5fb637f2847b19242da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 01:07:39 +0200 Subject: [PATCH 039/122] Test MutableSubscriptionSet.remove same query, different classes --- test/subscription_test.dart | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index d716574b3..1f2152817 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -88,15 +88,16 @@ Future main([List? args]) async { expect(subscriptions.find(query), isNotNull); }); - testSubscriptions('MutableSubscriptionSet.add (named)', (realm) { + testSubscriptions('MutableSubscriptionSet.add named', (realm) { final subscriptions = realm.subscriptions; const name = 'some name'; + late Subscription s; subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realm.all(), name: name); + s = mutableSubscriptions.add(realm.all(), name: name); }); expect(subscriptions, isNotEmpty); - expect(subscriptions.findByName(name), isNotNull); + expect(subscriptions.findByName(name), s); }); testSubscriptions('SubscriptionSet.find', (realm) { @@ -110,7 +111,7 @@ Future main([List? args]) async { }); expect(subscriptions.find(query), isNotNull); }); - + testSubscriptions('SubscriptionSet.find return match, even if named', (realm) { final subscriptions = realm.subscriptions; final query = realm.all(); @@ -341,6 +342,24 @@ Future main([List? args]) async { } }); + testSubscriptions('MutableSubscriptionSet.remove same query, different classes', (realm) { + final subscriptions = realm.subscriptions; + + late Subscription s; + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.all()); + s = mutableSubscriptions.add(realm.all()); + }); + + expect(subscriptions.length, 2); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.removeByQuery(realm.all()); + }); + + expect(subscriptions, [s]); + }); + testSubscriptions('Get subscriptions', (realm) async { final subscriptions = realm.subscriptions; From 72d36b6bd2971908d403d972675c5ef48d0292da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 01:27:39 +0200 Subject: [PATCH 040/122] Augment elementAt tests, and throw proper RangeError on out-of-bounds --- lib/src/subscription.dart | 3 ++- test/subscription_test.dart | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index bbe46da91..c75e8e6f1 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -16,7 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// -import 'dart:async'; +import 'dart:core'; import 'dart:collection'; import 'native/realm_core.dart'; @@ -174,6 +174,7 @@ abstract class SubscriptionSet with IterableMixin { @override Subscription elementAt(int index) { + RangeError.checkValidRange(index, null, length); return Subscription._(realmCore.subscriptionAt(this, index)); } diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 1f2152817..93966f9b2 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -216,6 +216,9 @@ Future main([List? args]) async { expect(s, subscriptions[index]); ++index; } + + expect(() => subscriptions[-1], throws()); + expect(() => subscriptions[1000], throws()); }); testSubscriptions('MutableSubscriptionSet.elementAt', (realm) { From cdcb1e19081778e3e65171d7be5c2cdda13a7ef5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 01:48:06 +0200 Subject: [PATCH 041/122] Test MutableSubscriptionSet.add illegal query --- test/subscription_test.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 93966f9b2..fd5ff2bf6 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -345,6 +345,19 @@ Future main([List? args]) async { } }); + testSubscriptions('MutableSubscriptionSet.add illegal query', (realm) async { + final subscriptions = realm.subscriptions; + + // Illegal query for subscription: + final query = realm.query('tasks.@count > 10'); + + subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(query); + }); + + expect(() async => await subscriptions.waitForSynchronization(), throws()); + }); + testSubscriptions('MutableSubscriptionSet.remove same query, different classes', (realm) { final subscriptions = realm.subscriptions; From 97d7627e77c2180bcce7578a371add194f025d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Thu, 12 May 2022 08:21:07 +0200 Subject: [PATCH 042/122] Add some missing docs --- CHANGELOG.md | 2 ++ lib/src/configuration.dart | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b90ccb42..1e57cfd9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### Breaking Changes * Made all `Configuration` fields final so they can only be initialized in the constructor. This better conveys the immutability of the configuration class. ([#455](https://github.com/realm/realm-dart/pull/455)) +* Removed `inMemory` field from `Configuration`. Use `Configuration.inMemory` factory instead. +* Deprecated `Configuration` constructor. Prefer `Configuration.local` instead. ### Enhancements * Added a property `Configuration.disableFormatUpgrade`. When set to `true`, opening a Realm with an older file format will throw an exception to avoid automatically upgrading it. ([#310](https://github.com/realm/realm-dart/pull/310)) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 534c0902b..3f8b2df77 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -96,8 +96,13 @@ abstract class Configuration { /// The [RealmSchema] for this [Configuration] final RealmSchema schema; + /// The key used to encrypt the entire [Realm]. + /// + /// A full 64byte (512bit) key for AES-256 encryption. + /// Once set, must be specified each time the file is used. final List? encryptionKey; + /// Constructs a [LocalConfiguration] @Deprecated('Use Configuration.local instead') factory Configuration( List schemaObjects, { @@ -110,6 +115,7 @@ abstract class Configuration { ShouldCompactCallback? shouldCompactCallback, }) = LocalConfiguration; + /// Constructs a [LocalConfiguration] factory Configuration.local( List schemaObjects, { InitialDataCallback? initialDataCallback, @@ -121,6 +127,7 @@ abstract class Configuration { ShouldCompactCallback? shouldCompactCallback, }) = LocalConfiguration; + /// Constructs a [InMemoryConfiguration] factory Configuration.inMemory( List schemaObjects, String identifier, { @@ -128,6 +135,7 @@ abstract class Configuration { String? path, }) = InMemoryConfiguration; + /// Constructs a [FlexibleSyncConfiguration] factory Configuration.flexibleSync( User user, List schemaObjects, { From 9104caf93f5eadc0e695c27a934b62ea717dc75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 10 May 2022 18:50:39 +0200 Subject: [PATCH 043/122] Add dart scheduler trampolines for sync session * realm_dart_sync_session_wait_for_download_completion * realm_dart_sync_session_wait_for_upload_completion * realm_dart_sync_session_register_progress_notifier * realm_dart_sync_session_register_connection_state_change_callback --- src/CMakeLists.txt | 8 ++- src/sync_session.cpp | 128 +++++++++++++++++++++++++++++++++++++++++++ src/sync_session.h | 72 ++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/sync_session.cpp create mode 100644 src/sync_session.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c26cae3e7..722379b31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,9 +8,10 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp - subscription_set.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp + sync_session.cpp ) set(HEADERS @@ -18,6 +19,7 @@ set(HEADERS realm_dart_scheduler.h realm-core/src/realm.h subscription_set.h + sync_session.h ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) diff --git a/src/sync_session.cpp b/src/sync_session.cpp new file mode 100644 index 000000000..4cd0ea4ac --- /dev/null +++ b/src/sync_session.cpp @@ -0,0 +1,128 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include "sync_session.h" +#include "event_loop_dispatcher.hpp" + +namespace realm::c_api { +namespace _1 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_sync_error_code_t* error) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(error); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API void realm_dart_sync_session_wait_for_download_completion(realm_sync_session_t* session, + realm_sync_download_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + realm_sync_session_wait_for_download_completion(session, _callback, u, _userdata_free); +} + +RLM_API void realm_dart_sync_session_wait_for_upload_completion(realm_sync_session_t* session, + realm_sync_upload_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + realm_sync_session_wait_for_upload_completion(session, _callback, u, _userdata_free); +} + +} // anonymous namespace + +namespace _2 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, uint64_t transferred_bytes, uint64_t total_bytes) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(transferred_bytes, total_bytes); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API uint64_t realm_dart_sync_session_register_progress_notifier(realm_sync_session_t* session, + realm_sync_progress_func_t callback, + realm_sync_progress_direction_e direction, + bool is_streaming, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_session_register_progress_notifier(session, _callback, direction, is_streaming, u, _userdata_free); +} + +} // anonymous namespace + +namespace _3 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(old_state, new_state); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, + realm_sync_connection_state_changed_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_session_register_connection_state_change_callback(session, _callback, u, _userdata_free); +} + +} // anonymous namespace + +} // namespace realm::c_api diff --git a/src/sync_session.h b/src/sync_session.h new file mode 100644 index 000000000..62bcbcdce --- /dev/null +++ b/src/sync_session.h @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_SYNC_SESSION_H +#define REALM_DART_SYNC_SESSION_H + +#include "realm.h" + +/** + * Register a callback that will be invoked when all pending downloads have completed. + */ +RLM_API void realm_dart_sync_session_wait_for_download_completion(realm_sync_session_t* session, + realm_sync_download_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked when all pending uploads have completed. + */ +RLM_API void realm_dart_sync_session_wait_for_upload_completion(realm_sync_session_t* session, + realm_sync_upload_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked every time the session reports progress. + * + * @param is_streaming If true, then the notifier will be called forever, and will + * always contain the most up-to-date number of downloadable or uploadable bytes. + * Otherwise, the number of downloaded or uploaded bytes will always be reported + * relative to the number of downloadable or uploadable bytes at the point in time + * when the notifier was registered. + * @return A token value that can be used to unregister the notifier. + */ +RLM_API uint64_t realm_dart_sync_session_register_progress_notifier(realm_sync_session_t* session, + realm_sync_progress_func_t callback, + realm_sync_progress_direction_e direction, + bool is_streaming, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked every time the session's connection state changes. + * + * @return A token value that can be used to unregister the callback. + */ +RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, + realm_sync_connection_state_changed_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + + +#endif // REALM_DART_SYNC_SESSION_H \ No newline at end of file From 3c47d5c2523b6782a1a35f15775f0729e057ef02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 20:14:21 +0200 Subject: [PATCH 044/122] Update bindings --- ffigen/config.yaml | 2 + ffigen/sync_session.h | 1 + lib/src/native/realm_bindings.dart | 159 +++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 120000 ffigen/sync_session.h diff --git a/ffigen/config.yaml b/ffigen/config.yaml index 22243f7d8..b0e1a9d72 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -8,12 +8,14 @@ headers: - 'realm_dart_scheduler.h' - 'realm_android_platform.h' - 'subscription_set.h' + - 'sync_session.h' include-directives: #generate only for these headers - 'realm.h' - 'realm_dart.h' - 'realm_dart_scheduler.h' - 'realm_android_platform.h' - 'subscription_set.h' + - 'sync_session.h' compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' diff --git a/ffigen/sync_session.h b/ffigen/sync_session.h new file mode 120000 index 000000000..2970acaff --- /dev/null +++ b/ffigen/sync_session.h @@ -0,0 +1 @@ +../src/sync_session.h \ No newline at end of file diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 2d4defecb..a61a515cc 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -8703,6 +8703,165 @@ class RealmLibrary { ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>(); + + /// Register a callback that will be invoked when all pending downloads have completed. + void realm_dart_sync_session_wait_for_download_completion( + ffi.Pointer session, + realm_sync_download_completion_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_wait_for_download_completion( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_wait_for_download_completionPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_download_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_wait_for_download_completion'); + late final _realm_dart_sync_session_wait_for_download_completion = + _realm_dart_sync_session_wait_for_download_completionPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_download_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked when all pending uploads have completed. + void realm_dart_sync_session_wait_for_upload_completion( + ffi.Pointer session, + realm_sync_upload_completion_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_wait_for_upload_completion( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_wait_for_upload_completionPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_upload_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_wait_for_upload_completion'); + late final _realm_dart_sync_session_wait_for_upload_completion = + _realm_dart_sync_session_wait_for_upload_completionPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_upload_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked every time the session reports progress. + /// + /// @param is_streaming If true, then the notifier will be called forever, and will + /// always contain the most up-to-date number of downloadable or uploadable bytes. + /// Otherwise, the number of downloaded or uploaded bytes will always be reported + /// relative to the number of downloadable or uploadable bytes at the point in time + /// when the notifier was registered. + /// @return A token value that can be used to unregister the notifier. + int realm_dart_sync_session_register_progress_notifier( + ffi.Pointer session, + realm_sync_progress_func_t callback, + int direction, + bool is_streaming, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_register_progress_notifier( + session, + callback, + direction, + is_streaming ? 1 : 0, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_register_progress_notifierPtr = _lookup< + ffi.NativeFunction< + ffi.Uint64 Function( + ffi.Pointer, + realm_sync_progress_func_t, + ffi.Int32, + ffi.Uint8, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_register_progress_notifier'); + late final _realm_dart_sync_session_register_progress_notifier = + _realm_dart_sync_session_register_progress_notifierPtr.asFunction< + int Function( + ffi.Pointer, + realm_sync_progress_func_t, + int, + int, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked every time the session's connection state changes. + /// + /// @return A token value that can be used to unregiser the callback. + int realm_dart_sync_session_register_connection_state_change_callback( + ffi.Pointer session, + realm_sync_connection_state_changed_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_register_connection_state_change_callback( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_register_connection_state_change_callbackPtr = + _lookup< + ffi.NativeFunction< + ffi.Uint64 Function( + ffi.Pointer, + realm_sync_connection_state_changed_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_register_connection_state_change_callback'); + late final _realm_dart_sync_session_register_connection_state_change_callback = + _realm_dart_sync_session_register_connection_state_change_callbackPtr + .asFunction< + int Function( + ffi.Pointer, + realm_sync_connection_state_changed_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); } class shared_realm extends ffi.Opaque {} From 5446e78b77ae9874dda7c4a3451ebc5d18ad470e Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 11 May 2022 23:24:02 +0200 Subject: [PATCH 045/122] Wire up some of the session API --- .vscode/settings.json | 4 + common/lib/src/realm_types.dart | 32 +++++- ffigen/config.yaml | 4 +- lib/src/app.dart | 15 +-- lib/src/native/realm_core.dart | 117 +++++++++++++++++++++ lib/src/realm_class.dart | 25 ++++- lib/src/session.dart | 177 ++++++++++++++++++++++++++++++++ lib/src/user.dart | 12 ++- test/app_test.dart | 5 +- test/session_test.dart | 124 ++++++++++++++++++++++ test/test.dart | 42 ++++++-- 11 files changed, 529 insertions(+), 28 deletions(-) create mode 100644 lib/src/session.dart create mode 100644 test/session_test.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 03c32b502..f4741ea32 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,4 +19,8 @@ "visibility": "hidden" } }, + "files.associations": { + "filesystem": "cpp", + "*.ipp": "cpp" + }, } \ No newline at end of file diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 903c53716..584128582 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -63,7 +63,7 @@ enum RealmCollectionType { class RealmError extends Error { final String? message; RealmError(String this.message); - + @override String toString() => "Realm error : $message"; } @@ -79,6 +79,36 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } +/// Thrown or reporeted if an error occurs during synchronization +/// {@category Sync} +class SyncError extends RealmError { + /// The code of the error + final int code; // TODO: this should be an enum + + /// The category of the error + final SyncErrorCategory category; + + SyncError(String message, this.category, this.code) : super(message); +} + +/// The category of a [SyncError]. +enum SyncErrorCategory { + /// The error originated from the client + client, + + /// The error originated from the connection + connection, + + /// The error originated from the session + session, + + /// Another low-level system error occurred + system, + + /// The category is unknown + unknown, +} + /// @nodoc class Decimal128 {} // TODO! diff --git a/ffigen/config.yaml b/ffigen/config.yaml index b0e1a9d72..1c372a084 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -1,4 +1,4 @@ -# Usage: dart run ffigen --config config.yaml +# Usage: dart run ffigen --config config.yaml name: RealmLibrary output: "../lib/src/native/realm_bindings.dart" headers: @@ -16,7 +16,7 @@ headers: - 'realm_android_platform.h' - 'subscription_set.h' - 'sync_session.h' -compiler-opts: +compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' typedef-map: diff --git a/lib/src/app.dart b/lib/src/app.dart index 12ba21eb5..1e9fc5f5b 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -115,15 +115,16 @@ class AppConfiguration { /// {@category Application} class App { final AppHandle _handle; - final AppConfiguration configuration; /// Create an app with a particular [AppConfiguration] - App(this.configuration) : _handle = realmCore.getApp(configuration); + App(AppConfiguration configuration) : this._(realmCore.getApp(configuration)); + + App._(this._handle); /// Logs in a user with the given credentials. Future logIn(Credentials credentials) async { var userHandle = await realmCore.logIn(this, credentials); - return UserInternal.create(this, userHandle); + return UserInternal.create(userHandle, app: this); } /// Gets the currently logged in [User]. If none exists, `null` is returned. @@ -132,19 +133,19 @@ class App { if (userHandle == null) { return null; } - return UserInternal.create(this, userHandle); + return UserInternal.create(userHandle, app: this); } /// Gets all currently logged in users. Iterable get users { - return realmCore.getUsers(this).map((handle) => UserInternal.create(this, handle)); + return realmCore.getUsers(this).map((handle) => UserInternal.create(handle, app: 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); } - + /// Switches the [currentUser] to the one specified in [user]. void switchUser(User user) { realmCore.switchUser(this, user); @@ -157,4 +158,6 @@ class App { /// @nodoc extension AppInternal on App { AppHandle get handle => _handle; + + static App create(AppHandle handle) => App._(handle); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 00e5fefb2..15639c965 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -35,6 +35,7 @@ import '../realm_object.dart'; import '../results.dart'; import '../subscription.dart'; import '../user.dart'; +import '../session.dart'; import 'realm_bindings.dart'; late RealmLibrary _realmLib; @@ -1346,6 +1347,11 @@ class _RealmCore { return userId; } + AppHandle userGetApp(UserHandle userHandle) { + // TODO: we don't have an API to get the app for a user - https://github.com/realm/realm-core/issues/5478 + return AppHandle._(nullptr); + } + List userGetIdentities(User user) { return using((arena) { //TODO: This approach is prone to race conditions. Fix this once Core changes how count is retrieved. @@ -1371,6 +1377,106 @@ class _RealmCore { _realmLib.invokeGetBool(() => _realmLib.realm_user_log_out(user.handle._pointer), "Logout failed"); return Future.value(); } + + SessionHandle realmGetSession(Realm realm) { + return SessionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_session_get(realm.handle._pointer))); + } + + 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 + 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); + } + + int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { + final isStreaming = mode == ProgressMode.reportIndefinitely; + // TODO: this should use the dart version of this method + return _realmLib.realm_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, + isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr); + } + + void sessionUnregisterProgressNotifier(Session session, int token) { + _realmLib.realm_sync_session_unregister_progress_notifier(session.handle._pointer, token); + } + + static void on_sync_progress(Pointer userdata, int transferred, int transferable) { + final SessionProgressNotificationsController? controller = userdata.toObject(isPersistent: true); + if (controller == null) { + return; + } + + controller.onProgress(transferred, transferable); + } + + Future sessionWaitForUpload(Session session) { + final completer = Completer(); + _realmLib.realm_dart_sync_session_wait_for_upload_completion( + session.handle._pointer, + Pointer.fromFunction(_waitCompletionCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + session.scheduler.handle._pointer, + ); + return completer.future; + } + + Future sessionWaitForDownload(Session session) { + final completer = Completer(); + _realmLib.realm_dart_sync_session_wait_for_download_completion( + session.handle._pointer, + Pointer.fromFunction(_waitCompletionCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + session.scheduler.handle._pointer, + ); + return completer.future; + } + + static void _waitCompletionCallback(Pointer userdata, Pointer errorCode) { + final completer = userdata.toObject>(isPersistent: true); + if (completer == null) { + return; + } + + if (errorCode != nullptr) { + completer.completeError(errorCode.toSyncError()); + } else { + completer.complete(); + } + } } class LastError { @@ -1508,6 +1614,10 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { Pointer get _mutablePointer => super._pointer.cast(); } +class SessionHandle extends ReleasableHandle { + SessionHandle._(Pointer pointer) : super(pointer, 24); // TODO: what is the size? +} + extension on List { Pointer toInt8Ptr(Allocator allocator) { return toUint8Ptr(allocator).cast(); @@ -1700,6 +1810,13 @@ extension on Pointer { } } +extension on Pointer { + SyncError toSyncError() { + final message = ref.message.cast().toRealmDartString()!; + return SyncError(message, SyncErrorCategory.values[ref.category], ref.value); + } +} + extension on Object { Pointer toWeakHandle() { return _realmLib.object_to_weak_handle(this); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index cfb23f834..e14910aa1 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -29,6 +29,7 @@ import 'native/realm_core.dart'; import 'realm_object.dart'; import 'results.dart'; import 'subscription.dart'; +import 'session.dart'; export 'package:realm_common/realm_common.dart' show @@ -37,6 +38,8 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, + SyncError, + SyncErrorCategory, RealmModel, RealmUnsupportedSetError, RealmStateError, @@ -65,6 +68,7 @@ export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState; +export 'session.dart' show Session, SessionState, ConnectionState; /// A [Realm] instance represents a `Realm` database. /// @@ -221,6 +225,12 @@ class Realm { /// All [RealmObject]s and `Realm ` collections are invalidated and can not be used. /// This method will not throw if called multiple times. void close() { + _syncSession?.handle.release(); + _syncSession = null; + + _subscriptions?.handle.release(); + _subscriptions = null; + realmCore.closeRealm(this); _scheduler.stop(); } @@ -276,7 +286,7 @@ class Realm { SubscriptionSet? _subscriptions; - /// The active [subscriptions] for this [Realm] + /// The active [SubscriptionSet] for this [Realm] SubscriptionSet get subscriptions { if (config is! FlexibleSyncConfiguration) throw RealmError('subscriptions is only valid on Realms opened with a FlexibleSyncConfiguration'); _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); @@ -284,6 +294,19 @@ class Realm { return _subscriptions!; } + Session? _syncSession; + + /// The [Session] for this [Realm]. The sync session is responsible for two-way synchronization + /// with MongoDB Atlas. If the [Realm] was is not synchronized, accessing this property will throw. + Session get syncSession { + if (config is! FlexibleSyncConfiguration) { + throw RealmError('session is only valid on synchronized Realms (i.e. opened with FlexibleSyncConfiguration)'); + } + + _syncSession ??= SessionInternal.create(realmCore.realmGetSession(this), scheduler); + return _syncSession!; + } + @override // ignore: hash_and_equals bool operator ==(Object other) { diff --git a/lib/src/session.dart b/lib/src/session.dart new file mode 100644 index 000000000..30ec4d064 --- /dev/null +++ b/lib/src/session.dart @@ -0,0 +1,177 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:async'; + +import '../realm.dart'; +import 'native/realm_core.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 +/// server. Sessions are always created by the SDK and vended out through various +/// APIs. The lifespans of sessions associated with Realms are managed automatically. +/// {@category Sync} +class Session { + final SessionHandle _handle; + + // This is very unpleasant, but the way we dispatch callbacks from native into + // dart requires us to pass down the scheudler. + final Scheduler _scheduler; + + /// The on-disk path of the file backing the [Realm] this [Session] represents + String get path => realmCore.sessionGetPath(this); + + /// 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); + + /// 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); + + /// The [User] that owns the [Realm] this [Session] is synchronizing. + User get user => UserInternal.create(realmCore.sessionGetUser(this)); + + Session._(this._handle, this._scheduler); + + /// 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); + + /// 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); + + /// Waits for the [Session] to finish all pending uploads. + Future waitForUpload() => realmCore.sessionWaitForUpload(this); + + /// Waits for the [Session] to finish all pending downloads. + Future waitForDownload() => realmCore.sessionWaitForDownload(this); + + /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. + Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { + final controller = SessionProgressNotificationsController(this, direction, mode); + return controller.createStream(); + } +} + +/// The current state of a [Session] object +enum SessionState { + /// The session is connected to the MongoDB Realm server and is actively transferring data. + active, + + /// The session is not currently communicating with the server. + inactive, +} + +/// The current connection state of a [Session] object +enum ConnectionState { + /// The session is disconnected from the MongoDB Realm server. + disconnected, + + /// The session is connecting to the MongoDB Realm server. + connecting, + + /// The session is connected to the MongoDB Realm server. + connected, +} + +/// The transfer direction (upload or download) tracked by a given progress notification subscription. +enum ProgressDirection { + /// Monitors upload progress. + upload, + + /// Monitors download progress. + download +} + +/// The desired behavior of a progress notification subscription. +enum ProgressMode { + /// The callback will be called forever, or until it is unregistered by disposing the subscription token. + /// Notifications will always report the latest number of transferred bytes, and the most up-to-date number of + /// total transferable bytes. + reportIndefinitely, + + /// The callback will, upon registration, store the total number of bytes to be transferred. When invoked, it will + /// always report the most up-to-date number of transferable bytes out of that original number of transferable bytes. + /// When the number of transferred bytes reaches or exceeds the number of transferable bytes, the callback will + /// be unregistered. + forCurrentlyOutstandingWork, +} + +/// A type containing information about the progress state at a given instant. +class SyncProgress { + /// The number of bytes that have been transferred since subscribing for progress notifications. + final int transferredBytes; + + /// The total number of bytes that have to be transferred since subscribing for progress notifications. + /// The difference between that number and [transferredBytes] gives you the number of bytes not yet + /// transferred. If the difference is 0, then all changes at the instant the callback fires have been + /// successfully transferred. + final int transferableBytes; + + SyncProgress._(this.transferredBytes, this.transferableBytes); +} + +extension SessionInternal on Session { + static Session create(SessionHandle handle, Scheduler scheduler) => Session._(handle, scheduler); + + SessionHandle get handle => _handle; + + Scheduler get scheduler => _scheduler; +} + +/// @nodoc +class SessionProgressNotificationsController { + final Session _session; + final ProgressDirection _direction; + final ProgressMode _mode; + + late int? _token; + late final StreamController _streamController; + + SessionProgressNotificationsController(this._session, this._direction, this._mode); + + Stream createStream() { + _streamController = StreamController.broadcast(onListen: _start, onCancel: _stop); + return _streamController.stream; + } + + void onProgress(int transferredBytes, int transferableBytes) { + _streamController.add(SyncProgress._(transferredBytes, transferableBytes)); + } + + void _start() { + if (_token != null) { + throw RealmStateError("Session progress subscription already started"); + } + + _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this); + } + + void _stop() { + if (_token == null) { + return; + } + + realmCore.sessionUnregisterProgressNotifier(_session, _token!); + } +} diff --git a/lib/src/user.dart b/lib/src/user.dart index 86846aa02..1c4f6706e 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -43,7 +43,7 @@ class User { if (data == null) { return null; } - + return jsonDecode(data); } @@ -58,7 +58,7 @@ class User { /// Linking a user with more credentials, mean the user can login either of these credentials. It also makes it possible to "upgrade" an anonymous user /// by linking it with e.g. Email/Password credentials. /// *Note: It is not possible to link two existing users of MongoDB Realm. The provided credentials must not have been used by another user.* - /// + /// /// The following snippet shows how to associate an email and password with an anonymous user allowing them to login on a different device. /// ```dart /// final app = App(configuration); @@ -72,7 +72,7 @@ class User { /// ``` Future linkCredentials(Credentials credentials) async { final userHandle = await realmCore.userLinkCredentials(app, this, credentials); - return UserInternal.create(app, userHandle); + return UserInternal.create(userHandle, app: app); } /// The current state of this [User]. @@ -136,5 +136,9 @@ extension UserIdentityInternal on UserIdentity { extension UserInternal on User { UserHandle get handle => _handle; - static User create(App app, UserHandle handle) => User._(app, handle); + static User create(UserHandle handle, {App? app}) { + app ??= AppInternal.create(realmCore.userGetApp(handle)); + + return User._(app, handle); + } } diff --git a/test/app_test.dart b/test/app_test.dart index 5eb2a51c3..e45cd404d 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -54,8 +54,7 @@ Future main([List? args]) async { test('App can be created', () async { final configuration = AppConfiguration(generateRandomString(10)); - final app = App(configuration); - expect(app.configuration, configuration); + App(configuration); }); baasTest('App log in', (configuration) async { @@ -125,7 +124,7 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.anonymous()); expect(app.currentUser, user1); - + final user2 = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); expect(app.currentUser, user2); diff --git a/test/session_test.dart b/test/session_test.dart new file mode 100644 index 000000000..1ac571182 --- /dev/null +++ b/test/session_test.dart @@ -0,0 +1,124 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:io'; + +import 'package:test/test.dart' hide test, throws; +import '../lib/realm.dart'; +import 'test.dart'; + +Future main([List? args]) async { + print("Current PID $pid"); + + await setupTests(args); + + test('Realm.syncSession throws on wrong configuration', () { + final config = Configuration.local([Task.schema]); + final realm = getRealm(config); + expect(() => realm.syncSession, throws()); + }); + + baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + expect(realm.syncSession, isNotNull); + expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession, realm.syncSession); + }); + + baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + expect(realm.syncSession, isNotNull); + expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession, realm.syncSession); + }); + + baasTest('SyncSession.user returns a valid user', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema]); + final realm = getRealm(config); + + expect(realm.syncSession.user, user); + expect(realm.syncSession.user.id, user.id); + }); + + baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema]); + + // Don't use getRealm because we want the Realm to survive + final realm = Realm(config); + + expect(realm.syncSession, isNotNull); + }, skip: 'crashes'); + + Future _validateSessionStates(Session session, {SessionState? sessionState, ConnectionState? connectionState}) async { + if (sessionState != null) { + expect(session.state.name, sessionState.name); + } + + if (connectionState != null) { + // The connection requires a bit of time to update its state + await Future.delayed(Duration(milliseconds: 100)); + expect(session.connectionState.name, connectionState.name); + } + } + + baasTest('SyncSession.pause/resume', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive, connectionState: ConnectionState.disconnected); + + realm.syncSession.resume(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + }); + + baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + + // This should not do anything + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + }); + + baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + + realm.syncSession.resume(); + realm.syncSession.resume(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + }); +} diff --git a/test/test.dart b/test/test.dart index f8676c228..d59f7b08b 100644 --- a/test/test.dart +++ b/test/test.dart @@ -147,7 +147,7 @@ Future setupTests(List? args) async { setUp(() { final path = generateRandomRealmPath(); Configuration.defaultPath = path; - + addTearDown(() async { final paths = HashSet(); paths.add(path); @@ -228,7 +228,7 @@ Future setupBaas() async { if (baasUrl == null) { return; } - + final cluster = Platform.environment['BAAS_CLUSTER']; final apiKey = Platform.environment['BAAS_API_KEY']; final privateApiKey = Platform.environment['BAAS_PRIVATE_API_KEY']; @@ -256,18 +256,38 @@ Future baasTest( } test(name, () async { - final app = baasApps[appName.name] ?? - baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); - final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); - final appConfig = AppConfiguration( - app.clientAppId, - baseUrl: url, - baseFilePath: temporaryDir, - ); - return await testFunction(appConfig); + final config = await getAppConfig(appName: appName); + return await testFunction(config); }, skip: skip); } +Future getAppConfig({AppNames appName = AppNames.flexible}) async { + final app = baasApps[appName.name] ?? + baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); + + final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); + return AppConfiguration( + app.clientAppId, + baseUrl: Uri.parse(Platform.environment['BAAS_URL']!), + baseFilePath: temporaryDir, + ); +} + +Future getIntegrationUser(App app) async { + final email = 'realm_tests_do_autoverify_${generateRandomString(10)}@realm.io'; + final password = 'password'; + await app.emailPasswordAuthProvider.registerUser(email, password); + + return await loginWithRetry(app, Credentials.emailPassword(email, password)); +} + +Future getIntegrationRealm(List schemas, {App? app}) async { + app ??= App(await getAppConfig()); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, schemas); + return getRealm(config); +} + Future loginWithRetry(App app, Credentials credentials, {int retryCount = 3}) async { try { return await app.logIn(credentials); From ac83b8ec577940aa44e48daa24df3dc530ea066d Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 12 May 2022 01:18:59 +0200 Subject: [PATCH 046/122] Use the dart trampoline --- lib/src/native/realm_core.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 15639c965..9ac3f4b58 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1423,9 +1423,8 @@ class _RealmCore { int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { final isStreaming = mode == ProgressMode.reportIndefinitely; - // TODO: this should use the dart version of this method - return _realmLib.realm_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, - isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr); + return _realmLib.realm_dart_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, + isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr, session.scheduler.handle._pointer); } void sessionUnregisterProgressNotifier(Session session, int token) { From f33f9838b24d332b5dbe2fcc080ef52710d34193 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 17 May 2022 01:44:21 +0300 Subject: [PATCH 047/122] FlexibleSyncConfig ErrorHandle implementation --- common/lib/src/realm_types.dart | 43 +++++++++++++++++++++--------- lib/src/configuration.dart | 10 +++++-- lib/src/native/realm_core.dart | 47 +++++++++++++++++++++++++++++---- lib/src/realm_class.dart | 1 + lib/src/session.dart | 3 ++- 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 584128582..01571ecfe 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -79,18 +79,6 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } -/// Thrown or reporeted if an error occurs during synchronization -/// {@category Sync} -class SyncError extends RealmError { - /// The code of the error - final int code; // TODO: this should be an enum - - /// The category of the error - final SyncErrorCategory category; - - SyncError(String message, this.category, this.code) : super(message); -} - /// The category of a [SyncError]. enum SyncErrorCategory { /// The error originated from the client @@ -109,6 +97,37 @@ enum SyncErrorCategory { unknown, } +/// Thrown or reporeted if an error occurs during synchronization +/// {@category Sync} +class SyncErrorCode extends RealmError { + /// The code of the error + final int code; // TODO: this should be an enum + + /// The category of the error + final SyncErrorCategory category; + + SyncErrorCode(String message, this.category, this.code) : super(message); +} + +class SyncError extends RealmError { + final SyncErrorCode errorCode; + final bool isFatal; + final bool isUnrecognizedByClient; + final bool isClientResetRequested; + final Map userInfoMap; + final int userInfoLength; + + SyncError( + this.errorCode, + String message, + this.isFatal, + this.isUnrecognizedByClient, + this.isClientResetRequested, + this.userInfoMap, + this.userInfoLength, + ) : super(message); +} + /// @nodoc class Decimal128 {} // TODO! diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 3f8b2df77..08a0ed83b 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as _path; @@ -44,6 +45,8 @@ typedef ShouldCompactCallback = bool Function(int totalSize, int usedSize); /// Realms, even if all objects in the Realm are deleted. typedef InitialDataCallback = void Function(Realm realm); +typedef ErrorHandlerCallback = void Function(SessionHandle user, SyncError error); + /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration { @@ -96,8 +99,8 @@ abstract class Configuration { /// The [RealmSchema] for this [Configuration] final RealmSchema schema; - /// The key used to encrypt the entire [Realm]. - /// + /// The key used to encrypt the entire [Realm]. + /// /// A full 64byte (512bit) key for AES-256 encryption. /// Once set, must be specified each time the file is used. final List? encryptionKey; @@ -208,11 +211,14 @@ class FlexibleSyncConfiguration extends Configuration { SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; + final ErrorHandlerCallback? errorHandlerCallback; + FlexibleSyncConfiguration( this.user, List schemaObjects, { String? fifoFilesFallbackPath, String? path, + this.errorHandlerCallback, }) : super._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 9ac3f4b58..ffd1cf72c 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -190,6 +190,8 @@ class _RealmCore { 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_error_handler( + syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), syncConfigPtr.toPersistentHandle(), nullptr); _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { _realmLib.realm_release(syncConfigPtr.cast()); @@ -394,6 +396,16 @@ class _RealmCore { return config.shouldCompactCallback!(totalSize, usedSize) ? TRUE : FALSE; } + static void _syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { + final FlexibleSyncConfiguration? syncConfig = userdata.toObject(isPersistent: true); + if (syncConfig == null) { + return; + } + if (syncConfig.errorHandlerCallback != null) { + final syncError = error.toSyncError(); + syncConfig.errorHandlerCallback!(SessionHandle._(user), syncError); + } + } SchedulerHandle createScheduler(int isolateId, int sendPort) { final schedulerPtr = _realmLib.realm_dart_create_scheduler(isolateId, sendPort); @@ -1464,14 +1476,14 @@ class _RealmCore { return completer.future; } - static void _waitCompletionCallback(Pointer userdata, Pointer errorCode) { + static void _waitCompletionCallback(Pointer userdata, Pointer errorCode) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; } if (errorCode != nullptr) { - completer.completeError(errorCode.toSyncError()); + completer.completeError(errorCode.ref.toSyncErrorCode()); } else { completer.complete(); } @@ -1809,10 +1821,35 @@ extension on Pointer { } } -extension on Pointer { +extension on realm_sync_error { SyncError toSyncError() { - final message = ref.message.cast().toRealmDartString()!; - return SyncError(message, SyncErrorCategory.values[ref.category], ref.value); + final messageText = detailed_message.cast().toRealmDartString()!; + final SyncErrorCode errorCode = error_code.toSyncErrorCode(); + final isFatal = is_fatal == 0 ? false : true; + final isUnrecognizedByClient = is_unrecognized_by_client == 0 ? false : true; + final isClientResetRequested = is_client_reset_requested == 0 ? false : true; + final userInfoMapKey = user_info_map.ref.key.cast().toRealmDartString()!; + final userInfoMapValue = user_info_map.ref.value.cast().toRealmDartString()!; + final userInfoMap = {}; + userInfoMap[userInfoMapKey] = userInfoMapValue; + final userInfoLength = user_info_length; + + return SyncError( + errorCode, + messageText, + isFatal, + isUnrecognizedByClient, + isClientResetRequested, + userInfoMap, + userInfoLength, + ); + } +} + +extension on realm_sync_error_code { + SyncErrorCode toSyncErrorCode() { + final messageText = message.cast().toRealmDartString()!; + return SyncErrorCode(messageText, SyncErrorCategory.values[category], value); } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index e14910aa1..c07f70004 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -38,6 +38,7 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, + SyncErrorCode, SyncError, SyncErrorCategory, RealmModel, diff --git a/lib/src/session.dart b/lib/src/session.dart index 30ec4d064..b4a40141b 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -50,7 +50,7 @@ class Session { User get user => UserInternal.create(realmCore.sessionGetUser(this)); Session._(this._handle, this._scheduler); - + /// 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); @@ -137,6 +137,7 @@ extension SessionInternal on Session { SessionHandle get handle => _handle; Scheduler get scheduler => _scheduler; + } /// @nodoc From 69b5f8fca5b5c7eb24c606d409651685c99e65c9 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 01:06:38 +0300 Subject: [PATCH 048/122] Code review changes --- common/lib/src/realm_types.dart | 24 ++++++++---------------- lib/src/configuration.dart | 3 +-- lib/src/native/realm_core.dart | 33 ++++++++++++--------------------- lib/src/realm_class.dart | 2 +- 4 files changed, 22 insertions(+), 40 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 01571ecfe..80875d507 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -99,33 +99,25 @@ enum SyncErrorCategory { /// Thrown or reporeted if an error occurs during synchronization /// {@category Sync} -class SyncErrorCode extends RealmError { +class SyncError extends RealmError { /// The code of the error final int code; // TODO: this should be an enum /// The category of the error final SyncErrorCategory category; - SyncErrorCode(String message, this.category, this.code) : super(message); + SyncError(String message, this.category, this.code) : super(message); } -class SyncError extends RealmError { - final SyncErrorCode errorCode; +class SessionError extends SyncError { final bool isFatal; - final bool isUnrecognizedByClient; - final bool isClientResetRequested; - final Map userInfoMap; - final int userInfoLength; - SyncError( - this.errorCode, + SessionError( String message, - this.isFatal, - this.isUnrecognizedByClient, - this.isClientResetRequested, - this.userInfoMap, - this.userInfoLength, - ) : super(message); + this.isFatal, { + SyncErrorCategory category = SyncErrorCategory.unknown, + int code = 0, + }) : super(message, category, code); } /// @nodoc diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 08a0ed83b..06207c3fe 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -16,7 +16,6 @@ // //////////////////////////////////////////////////////////////////////////////// -import 'dart:async'; import 'dart:io'; import 'package:path/path.dart' as _path; @@ -45,7 +44,7 @@ typedef ShouldCompactCallback = bool Function(int totalSize, int usedSize); /// Realms, even if all objects in the Realm are deleted. typedef InitialDataCallback = void Function(Realm realm); -typedef ErrorHandlerCallback = void Function(SessionHandle user, SyncError error); +typedef ErrorHandlerCallback = void Function(SessionError error); /// Configuration used to create a [Realm] instance /// {@category Configuration} diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index ffd1cf72c..923126bc9 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -396,14 +396,15 @@ class _RealmCore { return config.shouldCompactCallback!(totalSize, usedSize) ? TRUE : FALSE; } + static void _syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { final FlexibleSyncConfiguration? syncConfig = userdata.toObject(isPersistent: true); if (syncConfig == null) { return; } if (syncConfig.errorHandlerCallback != null) { - final syncError = error.toSyncError(); - syncConfig.errorHandlerCallback!(SessionHandle._(user), syncError); + final sessionError = error.toSessionError(); + syncConfig.errorHandlerCallback!(sessionError); } } @@ -1483,7 +1484,7 @@ class _RealmCore { } if (errorCode != nullptr) { - completer.completeError(errorCode.ref.toSyncErrorCode()); + completer.completeError(errorCode.ref.toSyncError()); } else { completer.complete(); } @@ -1822,34 +1823,24 @@ extension on Pointer { } extension on realm_sync_error { - SyncError toSyncError() { + SessionError toSessionError() { final messageText = detailed_message.cast().toRealmDartString()!; - final SyncErrorCode errorCode = error_code.toSyncErrorCode(); + final SyncError errorCode = error_code.toSyncError(); final isFatal = is_fatal == 0 ? false : true; - final isUnrecognizedByClient = is_unrecognized_by_client == 0 ? false : true; - final isClientResetRequested = is_client_reset_requested == 0 ? false : true; - final userInfoMapKey = user_info_map.ref.key.cast().toRealmDartString()!; - final userInfoMapValue = user_info_map.ref.value.cast().toRealmDartString()!; - final userInfoMap = {}; - userInfoMap[userInfoMapKey] = userInfoMapValue; - final userInfoLength = user_info_length; - - return SyncError( - errorCode, + + return SessionError( messageText, isFatal, - isUnrecognizedByClient, - isClientResetRequested, - userInfoMap, - userInfoLength, + category: errorCode.category, + code: errorCode.code, ); } } extension on realm_sync_error_code { - SyncErrorCode toSyncErrorCode() { + SyncError toSyncError() { final messageText = message.cast().toRealmDartString()!; - return SyncErrorCode(messageText, SyncErrorCategory.values[category], value); + return SyncError(messageText, SyncErrorCategory.values[category], value); } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index c07f70004..98d589637 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -38,8 +38,8 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, - SyncErrorCode, SyncError, + SessionError, SyncErrorCategory, RealmModel, RealmUnsupportedSetError, From 18542f13d70eba9c38180172e26b5648955ea378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 10 May 2022 18:50:39 +0200 Subject: [PATCH 049/122] Add dart scheduler trampolines for sync session * realm_dart_sync_session_wait_for_download_completion * realm_dart_sync_session_wait_for_upload_completion * realm_dart_sync_session_register_progress_notifier * realm_dart_sync_session_register_connection_state_change_callback --- src/CMakeLists.txt | 8 ++- src/sync_session.cpp | 128 +++++++++++++++++++++++++++++++++++++++++++ src/sync_session.h | 72 ++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/sync_session.cpp create mode 100644 src/sync_session.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c26cae3e7..722379b31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,9 +8,10 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp - subscription_set.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp + sync_session.cpp ) set(HEADERS @@ -18,6 +19,7 @@ set(HEADERS realm_dart_scheduler.h realm-core/src/realm.h subscription_set.h + sync_session.h ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) diff --git a/src/sync_session.cpp b/src/sync_session.cpp new file mode 100644 index 000000000..4cd0ea4ac --- /dev/null +++ b/src/sync_session.cpp @@ -0,0 +1,128 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include "sync_session.h" +#include "event_loop_dispatcher.hpp" + +namespace realm::c_api { +namespace _1 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_sync_error_code_t* error) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(error); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API void realm_dart_sync_session_wait_for_download_completion(realm_sync_session_t* session, + realm_sync_download_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + realm_sync_session_wait_for_download_completion(session, _callback, u, _userdata_free); +} + +RLM_API void realm_dart_sync_session_wait_for_upload_completion(realm_sync_session_t* session, + realm_sync_upload_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + realm_sync_session_wait_for_upload_completion(session, _callback, u, _userdata_free); +} + +} // anonymous namespace + +namespace _2 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, uint64_t transferred_bytes, uint64_t total_bytes) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(transferred_bytes, total_bytes); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API uint64_t realm_dart_sync_session_register_progress_notifier(realm_sync_session_t* session, + realm_sync_progress_func_t callback, + realm_sync_progress_direction_e direction, + bool is_streaming, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_session_register_progress_notifier(session, _callback, direction, is_streaming, u, _userdata_free); +} + +} // anonymous namespace + +namespace _3 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(old_state, new_state); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, + realm_sync_connection_state_changed_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_session_register_connection_state_change_callback(session, _callback, u, _userdata_free); +} + +} // anonymous namespace + +} // namespace realm::c_api diff --git a/src/sync_session.h b/src/sync_session.h new file mode 100644 index 000000000..62bcbcdce --- /dev/null +++ b/src/sync_session.h @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_SYNC_SESSION_H +#define REALM_DART_SYNC_SESSION_H + +#include "realm.h" + +/** + * Register a callback that will be invoked when all pending downloads have completed. + */ +RLM_API void realm_dart_sync_session_wait_for_download_completion(realm_sync_session_t* session, + realm_sync_download_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked when all pending uploads have completed. + */ +RLM_API void realm_dart_sync_session_wait_for_upload_completion(realm_sync_session_t* session, + realm_sync_upload_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked every time the session reports progress. + * + * @param is_streaming If true, then the notifier will be called forever, and will + * always contain the most up-to-date number of downloadable or uploadable bytes. + * Otherwise, the number of downloaded or uploaded bytes will always be reported + * relative to the number of downloadable or uploadable bytes at the point in time + * when the notifier was registered. + * @return A token value that can be used to unregister the notifier. + */ +RLM_API uint64_t realm_dart_sync_session_register_progress_notifier(realm_sync_session_t* session, + realm_sync_progress_func_t callback, + realm_sync_progress_direction_e direction, + bool is_streaming, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked every time the session's connection state changes. + * + * @return A token value that can be used to unregister the callback. + */ +RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, + realm_sync_connection_state_changed_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + + +#endif // REALM_DART_SYNC_SESSION_H \ No newline at end of file From b154604aff1b84ca136d70a611ba379e6898ae14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 20:14:21 +0200 Subject: [PATCH 050/122] Update bindings --- ffigen/config.yaml | 2 + ffigen/sync_session.h | 1 + lib/src/native/realm_bindings.dart | 159 +++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 120000 ffigen/sync_session.h diff --git a/ffigen/config.yaml b/ffigen/config.yaml index 22243f7d8..b0e1a9d72 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -8,12 +8,14 @@ headers: - 'realm_dart_scheduler.h' - 'realm_android_platform.h' - 'subscription_set.h' + - 'sync_session.h' include-directives: #generate only for these headers - 'realm.h' - 'realm_dart.h' - 'realm_dart_scheduler.h' - 'realm_android_platform.h' - 'subscription_set.h' + - 'sync_session.h' compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' diff --git a/ffigen/sync_session.h b/ffigen/sync_session.h new file mode 120000 index 000000000..2970acaff --- /dev/null +++ b/ffigen/sync_session.h @@ -0,0 +1 @@ +../src/sync_session.h \ No newline at end of file diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 6e70c8257..163e1aa89 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -8722,6 +8722,165 @@ class RealmLibrary { ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>(); + + /// Register a callback that will be invoked when all pending downloads have completed. + void realm_dart_sync_session_wait_for_download_completion( + ffi.Pointer session, + realm_sync_download_completion_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_wait_for_download_completion( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_wait_for_download_completionPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_download_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_wait_for_download_completion'); + late final _realm_dart_sync_session_wait_for_download_completion = + _realm_dart_sync_session_wait_for_download_completionPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_download_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked when all pending uploads have completed. + void realm_dart_sync_session_wait_for_upload_completion( + ffi.Pointer session, + realm_sync_upload_completion_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_wait_for_upload_completion( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_wait_for_upload_completionPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_upload_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_wait_for_upload_completion'); + late final _realm_dart_sync_session_wait_for_upload_completion = + _realm_dart_sync_session_wait_for_upload_completionPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_upload_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked every time the session reports progress. + /// + /// @param is_streaming If true, then the notifier will be called forever, and will + /// always contain the most up-to-date number of downloadable or uploadable bytes. + /// Otherwise, the number of downloaded or uploaded bytes will always be reported + /// relative to the number of downloadable or uploadable bytes at the point in time + /// when the notifier was registered. + /// @return A token value that can be used to unregister the notifier. + int realm_dart_sync_session_register_progress_notifier( + ffi.Pointer session, + realm_sync_progress_func_t callback, + int direction, + bool is_streaming, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_register_progress_notifier( + session, + callback, + direction, + is_streaming ? 1 : 0, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_register_progress_notifierPtr = _lookup< + ffi.NativeFunction< + ffi.Uint64 Function( + ffi.Pointer, + realm_sync_progress_func_t, + ffi.Int32, + ffi.Uint8, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_register_progress_notifier'); + late final _realm_dart_sync_session_register_progress_notifier = + _realm_dart_sync_session_register_progress_notifierPtr.asFunction< + int Function( + ffi.Pointer, + realm_sync_progress_func_t, + int, + int, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked every time the session's connection state changes. + /// + /// @return A token value that can be used to unregiser the callback. + int realm_dart_sync_session_register_connection_state_change_callback( + ffi.Pointer session, + realm_sync_connection_state_changed_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_register_connection_state_change_callback( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_register_connection_state_change_callbackPtr = + _lookup< + ffi.NativeFunction< + ffi.Uint64 Function( + ffi.Pointer, + realm_sync_connection_state_changed_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_register_connection_state_change_callback'); + late final _realm_dart_sync_session_register_connection_state_change_callback = + _realm_dart_sync_session_register_connection_state_change_callbackPtr + .asFunction< + int Function( + ffi.Pointer, + realm_sync_connection_state_changed_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); } class shared_realm extends ffi.Opaque {} From ea63166b432289235b17e11cba71fcd1fdb2a2e7 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 11 May 2022 23:24:02 +0200 Subject: [PATCH 051/122] Wire up some of the session API --- .vscode/settings.json | 4 + common/lib/src/realm_types.dart | 32 +++++- ffigen/config.yaml | 4 +- lib/src/app.dart | 13 ++- lib/src/native/realm_core.dart | 117 +++++++++++++++++++++ lib/src/realm_class.dart | 25 ++++- lib/src/session.dart | 177 ++++++++++++++++++++++++++++++++ lib/src/user.dart | 8 +- test/app_test.dart | 5 +- test/session_test.dart | 124 ++++++++++++++++++++++ test/test.dart | 38 +++++-- 11 files changed, 524 insertions(+), 23 deletions(-) create mode 100644 lib/src/session.dart create mode 100644 test/session_test.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 03c32b502..f4741ea32 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,4 +19,8 @@ "visibility": "hidden" } }, + "files.associations": { + "filesystem": "cpp", + "*.ipp": "cpp" + }, } \ No newline at end of file diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 903c53716..584128582 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -63,7 +63,7 @@ enum RealmCollectionType { class RealmError extends Error { final String? message; RealmError(String this.message); - + @override String toString() => "Realm error : $message"; } @@ -79,6 +79,36 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } +/// Thrown or reporeted if an error occurs during synchronization +/// {@category Sync} +class SyncError extends RealmError { + /// The code of the error + final int code; // TODO: this should be an enum + + /// The category of the error + final SyncErrorCategory category; + + SyncError(String message, this.category, this.code) : super(message); +} + +/// The category of a [SyncError]. +enum SyncErrorCategory { + /// The error originated from the client + client, + + /// The error originated from the connection + connection, + + /// The error originated from the session + session, + + /// Another low-level system error occurred + system, + + /// The category is unknown + unknown, +} + /// @nodoc class Decimal128 {} // TODO! diff --git a/ffigen/config.yaml b/ffigen/config.yaml index b0e1a9d72..1c372a084 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -1,4 +1,4 @@ -# Usage: dart run ffigen --config config.yaml +# Usage: dart run ffigen --config config.yaml name: RealmLibrary output: "../lib/src/native/realm_bindings.dart" headers: @@ -16,7 +16,7 @@ headers: - 'realm_android_platform.h' - 'subscription_set.h' - 'sync_session.h' -compiler-opts: +compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' typedef-map: diff --git a/lib/src/app.dart b/lib/src/app.dart index 86491417a..ef9e1abb4 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -148,15 +148,16 @@ class AppConfiguration { /// {@category Application} class App { final AppHandle _handle; - final AppConfiguration configuration; /// Create an app with a particular [AppConfiguration] - App(this.configuration) : _handle = realmCore.getApp(configuration); + App(AppConfiguration configuration) : this._(realmCore.getApp(configuration)); + + App._(this._handle); /// Logs in a user with the given credentials. Future logIn(Credentials credentials) async { var userHandle = await realmCore.logIn(this, credentials); - return UserInternal.create(this, userHandle); + return UserInternal.create(userHandle, app: this); } /// Gets the currently logged in [User]. If none exists, `null` is returned. @@ -165,12 +166,12 @@ class App { if (userHandle == null) { return null; } - return UserInternal.create(this, userHandle); + return UserInternal.create(userHandle, app: this); } /// Gets all currently logged in users. Iterable get users { - return realmCore.getUsers(this).map((handle) => UserInternal.create(this, handle)); + return realmCore.getUsers(this).map((handle) => UserInternal.create(handle, app: 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. @@ -203,4 +204,6 @@ enum MetadataPersistenceMode { /// @nodoc extension AppInternal on App { AppHandle get handle => _handle; + + static App create(AppHandle handle) => App._(handle); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 1ea2b584c..b4823e310 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -35,6 +35,7 @@ import '../realm_object.dart'; import '../results.dart'; import '../subscription.dart'; import '../user.dart'; +import '../session.dart'; import 'realm_bindings.dart'; late RealmLibrary _realmLib; @@ -1345,6 +1346,11 @@ class _RealmCore { return userId; } + AppHandle userGetApp(UserHandle userHandle) { + // TODO: we don't have an API to get the app for a user - https://github.com/realm/realm-core/issues/5478 + return AppHandle._(nullptr); + } + List userGetIdentities(User user) { return using((arena) { //TODO: This approach is prone to race conditions. Fix this once Core changes how count is retrieved. @@ -1386,6 +1392,106 @@ class _RealmCore { final dynamic profileData = jsonDecode(data.cast().toRealmDartString(freeNativeMemory: true)!); return UserProfile(profileData as Map); } + + SessionHandle realmGetSession(Realm realm) { + return SessionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_session_get(realm.handle._pointer))); + } + + 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 + 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); + } + + int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { + final isStreaming = mode == ProgressMode.reportIndefinitely; + // TODO: this should use the dart version of this method + return _realmLib.realm_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, + isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr); + } + + void sessionUnregisterProgressNotifier(Session session, int token) { + _realmLib.realm_sync_session_unregister_progress_notifier(session.handle._pointer, token); + } + + static void on_sync_progress(Pointer userdata, int transferred, int transferable) { + final SessionProgressNotificationsController? controller = userdata.toObject(isPersistent: true); + if (controller == null) { + return; + } + + controller.onProgress(transferred, transferable); + } + + Future sessionWaitForUpload(Session session) { + final completer = Completer(); + _realmLib.realm_dart_sync_session_wait_for_upload_completion( + session.handle._pointer, + Pointer.fromFunction(_waitCompletionCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + session.scheduler.handle._pointer, + ); + return completer.future; + } + + Future sessionWaitForDownload(Session session) { + final completer = Completer(); + _realmLib.realm_dart_sync_session_wait_for_download_completion( + session.handle._pointer, + Pointer.fromFunction(_waitCompletionCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + session.scheduler.handle._pointer, + ); + return completer.future; + } + + static void _waitCompletionCallback(Pointer userdata, Pointer errorCode) { + final completer = userdata.toObject>(isPersistent: true); + if (completer == null) { + return; + } + + if (errorCode != nullptr) { + completer.completeError(errorCode.toSyncError()); + } else { + completer.complete(); + } + } } class LastError { @@ -1527,6 +1633,10 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { Pointer get _mutablePointer => super._pointer.cast(); } +class SessionHandle extends ReleasableHandle { + SessionHandle._(Pointer pointer) : super(pointer, 24); // TODO: what is the size? +} + extension on List { Pointer toInt8Ptr(Allocator allocator) { return toUint8Ptr(allocator).cast(); @@ -1719,6 +1829,13 @@ extension on Pointer { } } +extension on Pointer { + SyncError toSyncError() { + final message = ref.message.cast().toRealmDartString()!; + return SyncError(message, SyncErrorCategory.values[ref.category], ref.value); + } +} + extension on Object { Pointer toWeakHandle() { return _realmLib.object_to_weak_handle(this); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d8d7e7c97..876a33ae8 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -29,6 +29,7 @@ import 'native/realm_core.dart'; import 'realm_object.dart'; import 'results.dart'; import 'subscription.dart'; +import 'session.dart'; export 'package:realm_common/realm_common.dart' show @@ -37,6 +38,8 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, + SyncError, + SyncErrorCategory, RealmModel, RealmUnsupportedSetError, RealmStateError, @@ -65,6 +68,7 @@ export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState; +export 'session.dart' show Session, SessionState, ConnectionState; /// A [Realm] instance represents a `Realm` database. /// @@ -221,6 +225,12 @@ class Realm { /// All [RealmObject]s and `Realm ` collections are invalidated and can not be used. /// This method will not throw if called multiple times. void close() { + _syncSession?.handle.release(); + _syncSession = null; + + _subscriptions?.handle.release(); + _subscriptions = null; + realmCore.closeRealm(this); _scheduler.stop(); } @@ -275,7 +285,7 @@ class Realm { SubscriptionSet? _subscriptions; - /// The active [subscriptions] for this [Realm] + /// The active [SubscriptionSet] for this [Realm] SubscriptionSet get subscriptions { if (config is! FlexibleSyncConfiguration) throw RealmError('subscriptions is only valid on Realms opened with a FlexibleSyncConfiguration'); _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); @@ -283,6 +293,19 @@ class Realm { return _subscriptions!; } + Session? _syncSession; + + /// The [Session] for this [Realm]. The sync session is responsible for two-way synchronization + /// with MongoDB Atlas. If the [Realm] was is not synchronized, accessing this property will throw. + Session get syncSession { + if (config is! FlexibleSyncConfiguration) { + throw RealmError('session is only valid on synchronized Realms (i.e. opened with FlexibleSyncConfiguration)'); + } + + _syncSession ??= SessionInternal.create(realmCore.realmGetSession(this), scheduler); + return _syncSession!; + } + @override // ignore: hash_and_equals bool operator ==(Object other) { diff --git a/lib/src/session.dart b/lib/src/session.dart new file mode 100644 index 000000000..30ec4d064 --- /dev/null +++ b/lib/src/session.dart @@ -0,0 +1,177 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:async'; + +import '../realm.dart'; +import 'native/realm_core.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 +/// server. Sessions are always created by the SDK and vended out through various +/// APIs. The lifespans of sessions associated with Realms are managed automatically. +/// {@category Sync} +class Session { + final SessionHandle _handle; + + // This is very unpleasant, but the way we dispatch callbacks from native into + // dart requires us to pass down the scheudler. + final Scheduler _scheduler; + + /// The on-disk path of the file backing the [Realm] this [Session] represents + String get path => realmCore.sessionGetPath(this); + + /// 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); + + /// 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); + + /// The [User] that owns the [Realm] this [Session] is synchronizing. + User get user => UserInternal.create(realmCore.sessionGetUser(this)); + + Session._(this._handle, this._scheduler); + + /// 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); + + /// 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); + + /// Waits for the [Session] to finish all pending uploads. + Future waitForUpload() => realmCore.sessionWaitForUpload(this); + + /// Waits for the [Session] to finish all pending downloads. + Future waitForDownload() => realmCore.sessionWaitForDownload(this); + + /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. + Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { + final controller = SessionProgressNotificationsController(this, direction, mode); + return controller.createStream(); + } +} + +/// The current state of a [Session] object +enum SessionState { + /// The session is connected to the MongoDB Realm server and is actively transferring data. + active, + + /// The session is not currently communicating with the server. + inactive, +} + +/// The current connection state of a [Session] object +enum ConnectionState { + /// The session is disconnected from the MongoDB Realm server. + disconnected, + + /// The session is connecting to the MongoDB Realm server. + connecting, + + /// The session is connected to the MongoDB Realm server. + connected, +} + +/// The transfer direction (upload or download) tracked by a given progress notification subscription. +enum ProgressDirection { + /// Monitors upload progress. + upload, + + /// Monitors download progress. + download +} + +/// The desired behavior of a progress notification subscription. +enum ProgressMode { + /// The callback will be called forever, or until it is unregistered by disposing the subscription token. + /// Notifications will always report the latest number of transferred bytes, and the most up-to-date number of + /// total transferable bytes. + reportIndefinitely, + + /// The callback will, upon registration, store the total number of bytes to be transferred. When invoked, it will + /// always report the most up-to-date number of transferable bytes out of that original number of transferable bytes. + /// When the number of transferred bytes reaches or exceeds the number of transferable bytes, the callback will + /// be unregistered. + forCurrentlyOutstandingWork, +} + +/// A type containing information about the progress state at a given instant. +class SyncProgress { + /// The number of bytes that have been transferred since subscribing for progress notifications. + final int transferredBytes; + + /// The total number of bytes that have to be transferred since subscribing for progress notifications. + /// The difference between that number and [transferredBytes] gives you the number of bytes not yet + /// transferred. If the difference is 0, then all changes at the instant the callback fires have been + /// successfully transferred. + final int transferableBytes; + + SyncProgress._(this.transferredBytes, this.transferableBytes); +} + +extension SessionInternal on Session { + static Session create(SessionHandle handle, Scheduler scheduler) => Session._(handle, scheduler); + + SessionHandle get handle => _handle; + + Scheduler get scheduler => _scheduler; +} + +/// @nodoc +class SessionProgressNotificationsController { + final Session _session; + final ProgressDirection _direction; + final ProgressMode _mode; + + late int? _token; + late final StreamController _streamController; + + SessionProgressNotificationsController(this._session, this._direction, this._mode); + + Stream createStream() { + _streamController = StreamController.broadcast(onListen: _start, onCancel: _stop); + return _streamController.stream; + } + + void onProgress(int transferredBytes, int transferableBytes) { + _streamController.add(SyncProgress._(transferredBytes, transferableBytes)); + } + + void _start() { + if (_token != null) { + throw RealmStateError("Session progress subscription already started"); + } + + _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this); + } + + void _stop() { + if (_token == null) { + return; + } + + realmCore.sessionUnregisterProgressNotifier(_session, _token!); + } +} diff --git a/lib/src/user.dart b/lib/src/user.dart index 133f87ce4..cff8a1201 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -107,7 +107,7 @@ class User { /// ``` Future linkCredentials(Credentials credentials) async { final userHandle = await realmCore.userLinkCredentials(app, this, credentials); - return UserInternal.create(app, userHandle); + return UserInternal.create(userHandle, app: app); } @override @@ -188,5 +188,9 @@ extension UserIdentityInternal on UserIdentity { extension UserInternal on User { UserHandle get handle => _handle; - static User create(App app, UserHandle handle) => User._(app, handle); + static User create(UserHandle handle, {App? app}) { + app ??= AppInternal.create(realmCore.userGetApp(handle)); + + return User._(app, handle); + } } diff --git a/test/app_test.dart b/test/app_test.dart index dae88b954..5c6803b59 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -101,8 +101,7 @@ test('AppConfiguration can be created with defaults', () { test('App can be created', () async { final configuration = AppConfiguration(generateRandomString(10)); - final app = App(configuration); - expect(app.configuration, configuration); + App(configuration); }); baasTest('App log in', (configuration) async { @@ -172,7 +171,7 @@ test('AppConfiguration can be created with defaults', () { final user1 = await app.logIn(Credentials.anonymous()); expect(app.currentUser, user1); - + final user2 = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); expect(app.currentUser, user2); diff --git a/test/session_test.dart b/test/session_test.dart new file mode 100644 index 000000000..1ac571182 --- /dev/null +++ b/test/session_test.dart @@ -0,0 +1,124 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:io'; + +import 'package:test/test.dart' hide test, throws; +import '../lib/realm.dart'; +import 'test.dart'; + +Future main([List? args]) async { + print("Current PID $pid"); + + await setupTests(args); + + test('Realm.syncSession throws on wrong configuration', () { + final config = Configuration.local([Task.schema]); + final realm = getRealm(config); + expect(() => realm.syncSession, throws()); + }); + + baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + expect(realm.syncSession, isNotNull); + expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession, realm.syncSession); + }); + + baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + expect(realm.syncSession, isNotNull); + expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession, realm.syncSession); + }); + + baasTest('SyncSession.user returns a valid user', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema]); + final realm = getRealm(config); + + expect(realm.syncSession.user, user); + expect(realm.syncSession.user.id, user.id); + }); + + baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema]); + + // Don't use getRealm because we want the Realm to survive + final realm = Realm(config); + + expect(realm.syncSession, isNotNull); + }, skip: 'crashes'); + + Future _validateSessionStates(Session session, {SessionState? sessionState, ConnectionState? connectionState}) async { + if (sessionState != null) { + expect(session.state.name, sessionState.name); + } + + if (connectionState != null) { + // The connection requires a bit of time to update its state + await Future.delayed(Duration(milliseconds: 100)); + expect(session.connectionState.name, connectionState.name); + } + } + + baasTest('SyncSession.pause/resume', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive, connectionState: ConnectionState.disconnected); + + realm.syncSession.resume(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + }); + + baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + + // This should not do anything + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + }); + + baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + + realm.syncSession.resume(); + realm.syncSession.resume(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + }); +} diff --git a/test/test.dart b/test/test.dart index 9100b6782..d59f7b08b 100644 --- a/test/test.dart +++ b/test/test.dart @@ -256,18 +256,38 @@ Future baasTest( } test(name, () async { - final app = baasApps[appName.name] ?? - baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); - final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); - final appConfig = AppConfiguration( - app.clientAppId, - baseUrl: url, - baseFilePath: temporaryDir, - ); - return await testFunction(appConfig); + final config = await getAppConfig(appName: appName); + return await testFunction(config); }, skip: skip); } +Future getAppConfig({AppNames appName = AppNames.flexible}) async { + final app = baasApps[appName.name] ?? + baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); + + final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); + return AppConfiguration( + app.clientAppId, + baseUrl: Uri.parse(Platform.environment['BAAS_URL']!), + baseFilePath: temporaryDir, + ); +} + +Future getIntegrationUser(App app) async { + final email = 'realm_tests_do_autoverify_${generateRandomString(10)}@realm.io'; + final password = 'password'; + await app.emailPasswordAuthProvider.registerUser(email, password); + + return await loginWithRetry(app, Credentials.emailPassword(email, password)); +} + +Future getIntegrationRealm(List schemas, {App? app}) async { + app ??= App(await getAppConfig()); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, schemas); + return getRealm(config); +} + Future loginWithRetry(App app, Credentials credentials, {int retryCount = 3}) async { try { return await app.logIn(credentials); From 64eafddf08a46a070de73e616450beea8afa35f7 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 12 May 2022 01:18:59 +0200 Subject: [PATCH 052/122] Use the dart trampoline --- lib/src/native/realm_core.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b4823e310..137274432 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1438,9 +1438,8 @@ class _RealmCore { int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { final isStreaming = mode == ProgressMode.reportIndefinitely; - // TODO: this should use the dart version of this method - return _realmLib.realm_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, - isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr); + return _realmLib.realm_dart_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, + isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr, session.scheduler.handle._pointer); } void sessionUnregisterProgressNotifier(Session session, int token) { From 948e5efb02206485d6f095f269df77e983736857 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 16 May 2022 23:57:58 +0200 Subject: [PATCH 053/122] use correct config ctor --- test/session_test.dart | 4 ++-- test/test.dart | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/session_test.dart b/test/session_test.dart index 1ac571182..550cf4e6e 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -52,7 +52,7 @@ Future main([List? args]) async { baasTest('SyncSession.user returns a valid user', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.sync(user, [Task.schema]); final realm = getRealm(config); expect(realm.syncSession.user, user); @@ -62,7 +62,7 @@ Future main([List? args]) async { baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.sync(user, [Task.schema]); // Don't use getRealm because we want the Realm to survive final realm = Realm(config); diff --git a/test/test.dart b/test/test.dart index d59f7b08b..3c3ce5a60 100644 --- a/test/test.dart +++ b/test/test.dart @@ -102,7 +102,6 @@ class _Schedule { final tasks = <_Task>[]; } - String? testName; final baasApps = {}; final _openRealms = Queue(); @@ -284,7 +283,7 @@ Future getIntegrationUser(App app) async { Future getIntegrationRealm(List schemas, {App? app}) async { app ??= App(await getAppConfig()); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, schemas); + final config = Configuration.sync(user, schemas); return getRealm(config); } From 919cf6adab980c0bcd6e642336242b319ab6f662 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 17 May 2022 02:03:37 +0200 Subject: [PATCH 054/122] Add more tests --- lib/src/cli/deployapps/baas_client.dart | 2 +- lib/src/realm_class.dart | 2 +- lib/src/session.dart | 6 +- test/session_test.dart | 185 +++++++++++++++++++++++- test/test.dart | 34 ++++- test/test.g.dart | 101 +++++++++++++ 6 files changed, 319 insertions(+), 11 deletions(-) diff --git a/lib/src/cli/deployapps/baas_client.dart b/lib/src/cli/deployapps/baas_client.dart index 819117ae4..b18473828 100644 --- a/lib/src/cli/deployapps/baas_client.dart +++ b/lib/src/cli/deployapps/baas_client.dart @@ -182,7 +182,7 @@ class BaasClient { "flexible_sync": { "state": "enabled", "database_name": "flexible_sync_data", - "queryable_fields_names": ["TODO"], + "queryable_fields_names": ["differentiator"], "permissions": { "rules": {}, "defaultRoles": [ diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 876a33ae8..05a073fbc 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -68,7 +68,7 @@ export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState; -export 'session.dart' show Session, SessionState, ConnectionState; +export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress; /// A [Realm] instance represents a `Realm` database. /// diff --git a/lib/src/session.dart b/lib/src/session.dart index 30ec4d064..f59f9addb 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -145,7 +145,7 @@ class SessionProgressNotificationsController { final ProgressDirection _direction; final ProgressMode _mode; - late int? _token; + int? _token; late final StreamController _streamController; SessionProgressNotificationsController(this._session, this._direction, this._mode); @@ -157,6 +157,10 @@ class SessionProgressNotificationsController { void onProgress(int transferredBytes, int transferableBytes) { _streamController.add(SyncProgress._(transferredBytes, transferableBytes)); + + if (transferredBytes >= transferableBytes && _mode == ProgressMode.forCurrentlyOutstandingWork) { + _streamController.close(); + } } void _start() { diff --git a/test/session_test.dart b/test/session_test.dart index 550cf4e6e..3a3af54e3 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:async'; import 'dart:io'; import 'package:test/test.dart' hide test, throws; @@ -34,7 +35,7 @@ Future main([List? args]) async { }); baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); expect(realm.syncSession.path, realm.config.path); @@ -42,7 +43,7 @@ Future main([List? args]) async { }); baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); expect(realm.syncSession.path, realm.config.path); @@ -83,7 +84,7 @@ Future main([List? args]) async { } baasTest('SyncSession.pause/resume', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); @@ -97,7 +98,7 @@ Future main([List? args]) async { }); baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); @@ -112,7 +113,7 @@ Future main([List? args]) async { }); baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); @@ -121,4 +122,178 @@ Future main([List? args]) async { await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); }); + + baasTest('SyncSession.waitForUpload with no changes', (configuration) async { + final realm = await getIntegrationRealm(); + + await realm.syncSession.waitForUpload(); + + // Call it multiple times to make sure it doesn't throw + await realm.syncSession.waitForUpload(); + }); + + baasTest('SyncSession.waitForDownload with no changes', (configuration) async { + final realm = await getIntegrationRealm(); + + await realm.syncSession.waitForDownload(); + + // Call it multiple times to make sure it doesn't throw + await realm.syncSession.waitForDownload(); + }); + + baasTest('SyncSesison.waitForUpload with changes', (configuration) async { + final differentiator = ObjectId(); + + final realmA = await getIntegrationRealm(differentiator: differentiator); + final realmB = await getIntegrationRealm(differentiator: differentiator, path: generateRandomRealmPath()); + + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: 'abc')); + }); + + await realmA.syncSession.waitForUpload(); + await realmB.syncSession.waitForDownload(); + + expect(realmA.all().map((e) => e.stringProp), realmB.all().map((e) => e.stringProp)); + + realmB.write(() { + realmB.add(NullableTypes(ObjectId(), differentiator, stringProp: 'def')); + }); + + await realmB.syncSession.waitForUpload(); + await realmA.syncSession.waitForDownload(); + + expect(realmA.all().map((e) => e.stringProp), realmB.all().map((e) => e.stringProp)); + }); + + StreamProgressData subscribeToProgress(Realm realm, ProgressDirection direction, ProgressMode mode) { + final data = StreamProgressData(); + final stream = realm.syncSession.getProgressStream(direction, mode); + data.subscription = stream.listen((event) { + expect(event.transferredBytes, greaterThanOrEqualTo(data.transferredBytes)); + if (data.transferableBytes != 0) { + // We need to wait for the first event to store the total bytes we expect. + if (mode == ProgressMode.forCurrentlyOutstandingWork) { + // Transferable should not change after the first event + expect(event.transferableBytes, data.transferableBytes); + } else { + // For indefinite progress, we expect the transferable bytes to not decrease + expect(event.transferableBytes, greaterThanOrEqualTo(data.transferableBytes)); + } + } + + data.transferredBytes = event.transferredBytes; + data.transferableBytes = event.transferableBytes; + data.callbacksInvoked++; + }); + + data.subscription.onDone(() { + data.doneInvoked = true; + }); + + return data; + } + + Future validateData(StreamProgressData data, {bool expectDone = false}) async { + // Wait a little since the last event is sent asynchronously + await Future.delayed(Duration(milliseconds: 100)); + + expect(data.callbacksInvoked, greaterThan(0)); + expect(data.transferableBytes, greaterThan(0)); + expect(data.transferredBytes, greaterThan(0)); + if (expectDone) { + expect(data.transferredBytes, data.transferableBytes); + } else { + expect(data.transferredBytes, lessThanOrEqualTo(data.transferableBytes)); + } + expect(data.doneInvoked, expectDone); + } + + baasTest('SyncSession.getProgressStream forCurrentlyOutstandingWork', (configuration) async { + final differentiator = ObjectId(); + final realmA = await getIntegrationRealm(differentiator: differentiator); + final realmB = await getIntegrationRealm(differentiator: differentiator, path: generateRandomRealmPath()); + + for (var i = 0; i < 10; i++) { + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: generateRandomString(50))); + }); + } + + final uploadData = subscribeToProgress(realmA, ProgressDirection.upload, ProgressMode.forCurrentlyOutstandingWork); + + await realmA.syncSession.waitForUpload(); + + // Subscribe immediately after the upload to ensure we get the entire upload message as progress notifications + final downloadData = subscribeToProgress(realmB, ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); + + await validateData(uploadData, expectDone: true); + + await realmB.syncSession.waitForDownload(); + + await validateData(downloadData, expectDone: true); + + await uploadData.subscription.cancel(); + await downloadData.subscription.cancel(); + }); + + baasTest('SyncSession.getProgressStream reportIndefinitely', (configuration) async { + final differentiator = ObjectId(); + final realmA = await getIntegrationRealm(differentiator: differentiator); + final realmB = await getIntegrationRealm(differentiator: differentiator, path: generateRandomRealmPath()); + + for (var i = 0; i < 10; i++) { + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: generateRandomString(50))); + }); + } + + final uploadData = subscribeToProgress(realmA, ProgressDirection.upload, ProgressMode.reportIndefinitely); + final downloadData = subscribeToProgress(realmB, ProgressDirection.download, ProgressMode.reportIndefinitely); + + await realmA.syncSession.waitForUpload(); + await validateData(uploadData); + + await realmB.syncSession.waitForDownload(); + await validateData(downloadData); + + // Snapshot the current state, then add a new object. We should receive more notifications + final uploadSnapshot = StreamProgressData.snapshot(uploadData); + final downloadSnapshot = StreamProgressData.snapshot(downloadData); + + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: generateRandomString(50))); + }); + + await validateData(uploadData); + await validateData(downloadData); + + expect(uploadData.transferredBytes, greaterThan(uploadSnapshot.transferredBytes)); + expect(uploadData.transferableBytes, greaterThan(uploadSnapshot.transferableBytes)); + expect(uploadData.callbacksInvoked, greaterThan(uploadSnapshot.callbacksInvoked)); + + expect(downloadData.transferredBytes, greaterThan(downloadSnapshot.transferredBytes)); + expect(downloadData.transferableBytes, greaterThan(downloadSnapshot.transferableBytes)); + expect(downloadData.callbacksInvoked, greaterThan(downloadSnapshot.callbacksInvoked)); + + await uploadData.subscription.cancel(); + await downloadData.subscription.cancel(); + }); +} + +class StreamProgressData { + int transferredBytes; + int transferableBytes; + int callbacksInvoked; + bool doneInvoked; + late StreamSubscription subscription; + + StreamProgressData({this.transferableBytes = 0, this.transferredBytes = 0, this.callbacksInvoked = 0, this.doneInvoked = false}); + + StreamProgressData.snapshot(StreamProgressData other) + : this( + transferableBytes: other.transferableBytes, + callbacksInvoked: other.callbacksInvoked, + doneInvoked: other.doneInvoked, + transferredBytes: other.transferredBytes); } diff --git a/test/test.dart b/test/test.dart index 3c3ce5a60..087ee419a 100644 --- a/test/test.dart +++ b/test/test.dart @@ -102,6 +102,23 @@ class _Schedule { final tasks = <_Task>[]; } +@RealmModel() +class _NullableTypes { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; + + late ObjectId differentiator; + + late String? stringProp; + late bool? boolProp; + late DateTime? dateProp; + late double? doubleProp; + late ObjectId? objectIdProp; + late Uuid? uuidProp; + late int? intProp; +} + String? testName; final baasApps = {}; final _openRealms = Queue(); @@ -280,11 +297,22 @@ Future getIntegrationUser(App app) async { return await loginWithRetry(app, Credentials.emailPassword(email, password)); } -Future getIntegrationRealm(List schemas, {App? app}) async { +Future getIntegrationRealm({App? app, ObjectId? differentiator, String? path}) async { app ??= App(await getAppConfig()); final user = await getIntegrationUser(app); - final config = Configuration.sync(user, schemas); - return getRealm(config); + + // TODO: path will not be needed after https://github.com/realm/realm-dart/pull/574 + final config = Configuration.sync(user, [Task.schema, Schedule.schema, NullableTypes.schema], path: path); + final realm = getRealm(config); + if (differentiator != null) { + realm.subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.query('differentiator = \$0', [differentiator])); + }); + + await realm.subscriptions.waitForSynchronization(); + } + + return realm; } Future loginWithRetry(App app, Credentials credentials, {int retryCount = 3}) async { diff --git a/test/test.g.dart b/test/test.g.dart index 4033dffc9..0acc896b8 100644 --- a/test/test.g.dart +++ b/test/test.g.dart @@ -396,3 +396,104 @@ class Schedule extends _Schedule with RealmEntity, RealmObject { ]); } } + +class NullableTypes extends _NullableTypes with RealmEntity, RealmObject { + NullableTypes( + ObjectId id, + ObjectId differentiator, { + String? stringProp, + bool? boolProp, + DateTime? dateProp, + double? doubleProp, + ObjectId? objectIdProp, + Uuid? uuidProp, + int? intProp, + }) { + RealmObject.set(this, '_id', id); + RealmObject.set(this, 'differentiator', differentiator); + RealmObject.set(this, 'stringProp', stringProp); + RealmObject.set(this, 'boolProp', boolProp); + RealmObject.set(this, 'dateProp', dateProp); + RealmObject.set(this, 'doubleProp', doubleProp); + RealmObject.set(this, 'objectIdProp', objectIdProp); + RealmObject.set(this, 'uuidProp', uuidProp); + RealmObject.set(this, 'intProp', intProp); + } + + NullableTypes._(); + + @override + ObjectId get id => RealmObject.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => throw RealmUnsupportedSetError(); + + @override + ObjectId get differentiator => + RealmObject.get(this, 'differentiator') as ObjectId; + @override + set differentiator(ObjectId value) => + RealmObject.set(this, 'differentiator', value); + + @override + String? get stringProp => + RealmObject.get(this, 'stringProp') as String?; + @override + set stringProp(String? value) => RealmObject.set(this, 'stringProp', value); + + @override + bool? get boolProp => RealmObject.get(this, 'boolProp') as bool?; + @override + set boolProp(bool? value) => RealmObject.set(this, 'boolProp', value); + + @override + DateTime? get dateProp => + RealmObject.get(this, 'dateProp') as DateTime?; + @override + set dateProp(DateTime? value) => RealmObject.set(this, 'dateProp', value); + + @override + double? get doubleProp => + RealmObject.get(this, 'doubleProp') as double?; + @override + set doubleProp(double? value) => RealmObject.set(this, 'doubleProp', value); + + @override + ObjectId? get objectIdProp => + RealmObject.get(this, 'objectIdProp') as ObjectId?; + @override + set objectIdProp(ObjectId? value) => + RealmObject.set(this, 'objectIdProp', value); + + @override + Uuid? get uuidProp => RealmObject.get(this, 'uuidProp') as Uuid?; + @override + set uuidProp(Uuid? value) => RealmObject.set(this, 'uuidProp', value); + + @override + int? get intProp => RealmObject.get(this, 'intProp') as int?; + @override + set intProp(int? value) => RealmObject.set(this, 'intProp', value); + + @override + Stream> get changes => + RealmObject.getChanges(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(NullableTypes._); + return const SchemaObject(NullableTypes, 'NullableTypes', [ + SchemaProperty('_id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + SchemaProperty('differentiator', RealmPropertyType.objectid), + SchemaProperty('stringProp', RealmPropertyType.string, optional: true), + SchemaProperty('boolProp', RealmPropertyType.bool, optional: true), + SchemaProperty('dateProp', RealmPropertyType.timestamp, optional: true), + SchemaProperty('doubleProp', RealmPropertyType.double, optional: true), + SchemaProperty('objectIdProp', RealmPropertyType.objectid, + optional: true), + SchemaProperty('uuidProp', RealmPropertyType.uuid, optional: true), + SchemaProperty('intProp', RealmPropertyType.int, optional: true), + ]); + } +} From 90d39f511fc8b316885151fc4535b627288deca9 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 17 May 2022 02:15:05 +0200 Subject: [PATCH 055/122] formatting --- lib/src/realm_class.dart | 2 +- src/CMakeLists.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 05a073fbc..69c33ef1e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -296,7 +296,7 @@ class Realm { Session? _syncSession; /// The [Session] for this [Realm]. The sync session is responsible for two-way synchronization - /// with MongoDB Atlas. If the [Realm] was is not synchronized, accessing this property will throw. + /// with MongoDB Atlas. If the [Realm] is not synchronized, accessing this property will throw. Session get syncSession { if (config is! FlexibleSyncConfiguration) { throw RealmError('session is only valid on synchronized Realms (i.e. opened with FlexibleSyncConfiguration)'); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 722379b31..47ecb1bf9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,10 +8,10 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp - subscription_set.cpp - sync_session.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp + sync_session.cpp ) set(HEADERS From e2019522852f89793c510fa89e7e7819180398ac Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 13:47:44 +0200 Subject: [PATCH 056/122] Address PR feedback --- lib/src/app.dart | 6 ++--- lib/src/native/realm_core.dart | 8 +++---- lib/src/session.dart | 5 ++-- lib/src/subscription.dart | 14 ++++-------- lib/src/user.dart | 18 +++++++-------- test/session_test.dart | 42 ++++++++++++++++++++-------------- 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index ef9e1abb4..a2d90fba9 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -157,7 +157,7 @@ class App { /// Logs in a user with the given credentials. Future logIn(Credentials credentials) async { var userHandle = await realmCore.logIn(this, credentials); - return UserInternal.create(userHandle, app: this); + return UserInternal.create(userHandle, this); } /// Gets the currently logged in [User]. If none exists, `null` is returned. @@ -166,12 +166,12 @@ class App { if (userHandle == null) { return null; } - return UserInternal.create(userHandle, app: this); + return UserInternal.create(userHandle, this); } /// Gets all currently logged in users. Iterable get users { - return realmCore.getUsers(this).map((handle) => UserInternal.create(handle, app: this)); + return realmCore.getUsers(this).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. diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 137274432..c4f34da78 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1583,7 +1583,7 @@ class RealmNotificationTokenHandle extends ReleasableHandle { - RealmCallbackTokenHandle._(Pointer pointer) : super(pointer, 32); + RealmCallbackTokenHandle._(Pointer pointer) : super(pointer, 24); } class RealmCollectionChangesHandle extends Handle { @@ -1619,11 +1619,11 @@ class UserHandle extends Handle { } class SubscriptionHandle extends Handle { - SubscriptionHandle._(Pointer pointer) : super(pointer, 24); + SubscriptionHandle._(Pointer pointer) : super(pointer, 184); } class SubscriptionSetHandle extends ReleasableHandle { - SubscriptionSetHandle._(Pointer pointer) : super(pointer, 24); + SubscriptionSetHandle._(Pointer pointer) : super(pointer, 128); } class MutableSubscriptionSetHandle extends SubscriptionSetHandle { @@ -1633,7 +1633,7 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { } class SessionHandle extends ReleasableHandle { - SessionHandle._(Pointer pointer) : super(pointer, 24); // TODO: what is the size? + SessionHandle._(Pointer pointer) : super(pointer, 24); } extension on List { diff --git a/lib/src/session.dart b/lib/src/session.dart index f59f9addb..08bf8d654 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -35,7 +35,7 @@ class Session { final Scheduler _scheduler; /// The on-disk path of the file backing the [Realm] this [Session] represents - String get path => realmCore.sessionGetPath(this); + String get realmPath => realmCore.sessionGetPath(this); /// 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 @@ -105,7 +105,7 @@ enum ProgressDirection { /// The desired behavior of a progress notification subscription. enum ProgressMode { - /// The callback will be called forever, or until it is unregistered by disposing the subscription token. + /// The callback will be called forever, or until it is unregistered by closing the `Stream`. /// Notifications will always report the latest number of transferred bytes, and the most up-to-date number of /// total transferable bytes. reportIndefinitely, @@ -177,5 +177,6 @@ class SessionProgressNotificationsController { } realmCore.sessionUnregisterProgressNotifier(_session, _token!); + _token = null; } } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index bc5cdd70e..1112e232b 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -34,8 +34,6 @@ class Subscription { ObjectId get _id => realmCore.subscriptionId(this); /// Name of the [Subscription], if one was provided at creation time. - /// - /// Otherwise returns null. String? get name => realmCore.subscriptionName(this); /// Class name of objects the [Subscription] refers to. @@ -45,8 +43,8 @@ class Subscription { /// rather than the name of the generated Dart class. String get objectClassName => realmCore.subscriptionObjectClassName(this); - /// Query string that describes the [Subscription]. - /// + /// 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); @@ -62,7 +60,7 @@ class Subscription { if (identical(this, other)) return true; if (other is! Subscription) return false; // TODO: Don't work, issue with C-API - // return realmCore.subscriptionEquals(this, other); + // return realmCore.subscriptionEquals(this, other); return id == other.id; // <-- do this instead } } @@ -134,16 +132,12 @@ abstract class SubscriptionSet with IterableMixin { /// Finds an existing [Subscription] in this set by its query /// /// The [query] is represented by the corresponding [RealmResults] object. - /// - /// Returns null, if not found Subscription? find(RealmResults query) { final result = realmCore.findSubscriptionByResults(this, query); return result == null ? null : Subscription._(result); } /// Finds an existing [Subscription] in this set by name. - /// - /// Returns null, if not found Subscription? findByName(String name) { final result = realmCore.findSubscriptionByName(this, name); return result == null ? null : Subscription._(result); @@ -203,7 +197,7 @@ abstract class SubscriptionSet with IterableMixin { case SubscriptionSetState._bootstrapping: return SubscriptionSetState.pending; default: - return state; + return state; } } } diff --git a/lib/src/user.dart b/lib/src/user.dart index cff8a1201..ae8af4cdb 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -107,7 +107,7 @@ class User { /// ``` Future linkCredentials(Credentials credentials) async { final userHandle = await realmCore.userLinkCredentials(app, this, credentials); - return UserInternal.create(userHandle, app: app); + return UserInternal.create(userHandle, app); } @override @@ -148,25 +148,25 @@ class UserProfile { /// Gets the name of the [User]. String? get name => _data["name"] as String?; - + /// Gets the first name of the [User]. String? get firstName => _data["firstName"] as String?; /// Gets the last name of the [User]. String? get lastName => _data["lastName"] as String?; - + /// Gets the email of the [User]. String? get email => _data["email"] as String?; - + /// Gets the gender of the [User]. String? get gender => _data["gender"] as String?; - + /// Gets the birthday of the user. String? get birthDay => _data["birthDay"] as String?; - + /// Gets the minimum age of the [User]. String? get minAge => _data["minAge"] as String?; - + /// Gets the maximum age of the [User]. String? get maxAge => _data["maxAge"] as String?; @@ -174,7 +174,7 @@ class UserProfile { String? get pictureUrl => _data["pictureUrl"] as String?; /// Gets a profile property of the [User]. - dynamic operator[](String property) => _data[property]; + dynamic operator [](String property) => _data[property]; const UserProfile(this._data); } @@ -188,7 +188,7 @@ extension UserIdentityInternal on UserIdentity { extension UserInternal on User { UserHandle get handle => _handle; - static User create(UserHandle handle, {App? app}) { + static User create(UserHandle handle, [App? app]) { app ??= AppInternal.create(realmCore.userGetApp(handle)); return User._(app, handle); diff --git a/test/session_test.dart b/test/session_test.dart index 3a3af54e3..8b54666a5 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -38,7 +38,7 @@ Future main([List? args]) async { final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); - expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession.realmPath, realm.config.path); expect(realm.syncSession, realm.syncSession); }); @@ -46,7 +46,7 @@ Future main([List? args]) async { final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); - expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession.realmPath, realm.config.path); expect(realm.syncSession, realm.syncSession); }); @@ -71,56 +71,63 @@ Future main([List? args]) async { expect(realm.syncSession, isNotNull); }, skip: 'crashes'); - Future _validateSessionStates(Session session, {SessionState? sessionState, ConnectionState? connectionState}) async { - if (sessionState != null) { - expect(session.state.name, sessionState.name); + Future validateSessionStates(Session session, {SessionState? expectedSessionState, ConnectionState? expectedConnectionState}) async { + if (expectedSessionState != null) { + expect(session.state.name, expectedSessionState.name); } - if (connectionState != null) { - // The connection requires a bit of time to update its state - await Future.delayed(Duration(milliseconds: 100)); - expect(session.connectionState.name, connectionState.name); + if (expectedConnectionState != null) { + for (var i = 0; i < 5; i++) { + if (session.connectionState.name == expectedConnectionState.name) { + break; + } + + // The connection requires a bit of time to update its state + await Future.delayed(Duration(milliseconds: 100)); + } + + expect(session.connectionState.name, expectedConnectionState.name); } } baasTest('SyncSession.pause/resume', (configuration) async { final realm = await getIntegrationRealm(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active, expectedConnectionState: ConnectionState.connected); realm.syncSession.pause(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive, connectionState: ConnectionState.disconnected); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.inactive, expectedConnectionState: ConnectionState.disconnected); realm.syncSession.resume(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active, expectedConnectionState: ConnectionState.connected); }); baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { final realm = await getIntegrationRealm(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active); realm.syncSession.pause(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.inactive); // This should not do anything realm.syncSession.pause(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.inactive); }); baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { final realm = await getIntegrationRealm(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active); realm.syncSession.resume(); realm.syncSession.resume(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active); }); baasTest('SyncSession.waitForUpload with no changes', (configuration) async { @@ -297,3 +304,4 @@ class StreamProgressData { doneInvoked: other.doneInvoked, transferredBytes: other.transferredBytes); } +- \ No newline at end of file From a480f4640a9506245c6e4b1f98017ed568211925 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 13:54:09 +0200 Subject: [PATCH 057/122] Fix tests --- test/app_test.dart | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/test/app_test.dart b/test/app_test.dart index 5c6803b59..f41c61c4f 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -60,17 +60,19 @@ Future main([List? args]) async { expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1)); expect(appConfig.httpClient, httpClient); }); - -test('AppConfiguration can be created with defaults', () { + + test('AppConfiguration can be created with defaults', () { final appConfig = AppConfiguration('myapp1'); - final app = App(appConfig); - expect(app.configuration.appId, 'myapp1'); - expect(app.configuration.baseUrl, Uri.parse('https://realm.mongodb.com')); - expect(app.configuration.defaultRequestTimeout, const Duration(minutes: 1)); - expect(app.configuration.logLevel, LogLevel.error); - expect(app.configuration.metadataPersistenceMode, MetadataPersistenceMode.plaintext); - expect(app.configuration.maxConnectionTimeout, const Duration(minutes: 2)); - expect(app.configuration.httpClient, isNotNull); + expect(appConfig.appId, 'myapp1'); + expect(appConfig.baseUrl, Uri.parse('https://realm.mongodb.com')); + expect(appConfig.defaultRequestTimeout, const Duration(minutes: 1)); + expect(appConfig.logLevel, LogLevel.error); + expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.plaintext); + expect(appConfig.maxConnectionTimeout, const Duration(minutes: 2)); + expect(appConfig.httpClient, isNotNull); + + // Check that the app constructor works + App(appConfig); }); test('AppConfiguration can be created', () { @@ -88,15 +90,18 @@ test('AppConfiguration can be created with defaults', () { maxConnectionTimeout: const Duration(minutes: 1), httpClient: httpClient, ); - final app = App(appConfig); - expect(app.configuration.appId, 'myapp1'); - expect(app.configuration.baseFilePath.path, Directory.systemTemp.path); - expect(app.configuration.baseUrl, Uri.parse('https://not_re.al')); - expect(app.configuration.defaultRequestTimeout, const Duration(seconds: 2)); - expect(app.configuration.logLevel, LogLevel.info); - expect(app.configuration.metadataPersistenceMode, MetadataPersistenceMode.encrypted); - expect(app.configuration.maxConnectionTimeout, const Duration(minutes: 1)); - expect(app.configuration.httpClient, httpClient); + + expect(appConfig.appId, 'myapp1'); + expect(appConfig.baseFilePath.path, Directory.systemTemp.path); + expect(appConfig.baseUrl, Uri.parse('https://not_re.al')); + expect(appConfig.defaultRequestTimeout, const Duration(seconds: 2)); + expect(appConfig.logLevel, LogLevel.info); + expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.encrypted); + expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1)); + expect(appConfig.httpClient, httpClient); + + // Check that the app constructor works + App(appConfig); }); test('App can be created', () async { From af3293cbcb1a646e64b896d079b35a2a90968d02 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 14:55:15 +0300 Subject: [PATCH 058/122] Fix build errors --- lib/src/configuration.dart | 1 + src/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 06207c3fe..588f9db24 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -143,6 +143,7 @@ abstract class Configuration { List schemaObjects, { String? fifoFilesFallbackPath, String? path, + ErrorHandlerCallback? errorHandlerCallback, }) = FlexibleSyncConfiguration; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 722379b31..c455cf530 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,7 +23,7 @@ set(HEADERS ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) -target_compile_definitions(RealmFFIStatic PUBLIC -DRealm_EXPORTS) +target_compile_definitions(RealmFFIStatic PUBLIC -DRealm_EXPORTS -DREALM_ENABLE_SYNC=1) target_link_libraries(realm_dart dart-dl RealmFFIStatic Realm::Storage) From be0a234dc207de25924043c48623fc5d985ff01c Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 14:01:37 +0200 Subject: [PATCH 059/122] Store the http transport as persistent handle --- lib/src/native/realm_core.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c4f34da78..f3de1d358 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -876,8 +876,6 @@ class _RealmCore { _realmLib.realm_app_config_set_platform(handle._pointer, Platform.operatingSystem.toUtf8Ptr(arena)); _realmLib.realm_app_config_set_platform_version(handle._pointer, Platform.operatingSystemVersion.toUtf8Ptr(arena)); - //This sets the realm lib version instead of the SDK version. - //TODO: Read the SDK version from code generated version field _realmLib.realm_app_config_set_sdk_version(handle._pointer, libraryVersion.toUtf8Ptr(arena)); return handle; @@ -899,8 +897,8 @@ class _RealmCore { RealmHttpTransportHandle _createHttpTransport(HttpClient httpClient) { return RealmHttpTransportHandle._(_realmLib.realm_http_transport_new( Pointer.fromFunction(request_callback), - httpClient.toWeakHandle(), - nullptr, + httpClient.toPersistentHandle(), + _deletePersistentHandleFuncPtr, )); } From 6d322a765d5c96453a1a1e916e68fa077d0d8eaa Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 16:18:16 +0300 Subject: [PATCH 060/122] API doc --- CHANGELOG.md | 1 + common/lib/src/realm_types.dart | 3 +++ lib/src/configuration.dart | 13 ++++++++----- lib/src/native/realm_core.dart | 24 +++++++++++++++++++----- src/realm-core | 2 +- 5 files changed, 32 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05a687b22..854c30d4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ * Support user authentication provider type. ([#570](https://github.com/realm/realm-dart/pull/570)) * Support user profile data. ([#570](https://github.com/realm/realm-dart/pull/570)) * Support flexible synchronization. ([#496](https://github.com/realm/realm-dart/pull/496)) +* Support session error handler. ([#577](https://github.com/realm/realm-dart/pull/577)) ### Fixed * Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442)) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index cd31b449e..abea694be 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -79,7 +79,10 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } +/// An error type that describes a session-level error condition. +/// /// {@category Sync} class SessionError extends SyncError { + /// If true the received error is fatal. final bool isFatal; SessionError( diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index ecd195c12..457288e1b 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -44,7 +44,10 @@ typedef ShouldCompactCallback = bool Function(int totalSize, int usedSize); /// Realms, even if all objects in the Realm are deleted. typedef InitialDataCallback = void Function(Realm realm); -typedef ErrorHandlerCallback = void Function(SessionError error); +///The signature of a callback that will be invoked whenever a [SessionError] occurs for the synchronized Realm. +/// +/// Client reset errors will not be reported through this callback as they are handled by the set [ClientResetHandler]. +typedef SessionErrorHandler = void Function(SessionError error); /// Configuration used to create a [Realm] instance /// {@category Configuration} @@ -144,14 +147,14 @@ abstract class Configuration { List schemaObjects, { String? fifoFilesFallbackPath, String? path, - ErrorHandlerCallback? errorHandlerCallback, + SessionErrorHandler? sessionErrorHandler, }) => FlexibleSyncConfiguration._( user, schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, - errorHandlerCallback: errorHandlerCallback, + sessionErrorHandler: sessionErrorHandler, ); } @@ -219,14 +222,14 @@ class FlexibleSyncConfiguration extends Configuration { SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; - final ErrorHandlerCallback? errorHandlerCallback; + final SessionErrorHandler? sessionErrorHandler; FlexibleSyncConfiguration._( this.user, List schemaObjects, { String? fifoFilesFallbackPath, String? path, - this.errorHandlerCallback, + this.sessionErrorHandler, }) : super._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 4e75b300c..5643df89b 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -190,8 +190,7 @@ class _RealmCore { 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_error_handler( - syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), nullptr); + _realmLib.realm_sync_config_set_error_handler(syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), nullptr); _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { _realmLib.realm_release(syncConfigPtr.cast()); @@ -400,12 +399,12 @@ class _RealmCore { if (syncConfig == null) { return; } - if (syncConfig.errorHandlerCallback != null) { + if (syncConfig.sessionErrorHandler != null) { final sessionError = error.toSessionError(); - syncConfig.errorHandlerCallback!(sessionError); + syncConfig.sessionErrorHandler!(sessionError); } } - + SchedulerHandle createScheduler(int isolateId, int sendPort) { final schedulerPtr = _realmLib.realm_dart_create_scheduler(isolateId, sendPort); return SchedulerHandle._(schedulerPtr); @@ -1839,6 +1838,21 @@ extension on Pointer { } } +extension on realm_sync_error { + SessionError toSessionError() { + final messageText = detailed_message.cast().toRealmDartString()!; + final SyncErrorCategory errorCategory = SyncErrorCategory.values[error_code.category]; + final isFatal = is_fatal == 0 ? false : true; + + return SessionError( + messageText, + isFatal, + category: errorCategory, + code: error_code.value, + ); + } +} + extension on Pointer { SyncError toSyncError() { final message = ref.message.cast().toRealmDartString()!; diff --git a/src/realm-core b/src/realm-core index 8c2ad6fc8..c220e3ca5 160000 --- a/src/realm-core +++ b/src/realm-core @@ -1 +1 @@ -Subproject commit 8c2ad6fc800417784acbe238d41b8409ee3fe991 +Subproject commit c220e3ca59032024d641f8e3b0243064f90ff068 From 3388a9321b6498711936a04914f13b0fc511939d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Tue, 10 May 2022 18:50:39 +0200 Subject: [PATCH 061/122] Add dart scheduler trampolines for sync session * realm_dart_sync_session_wait_for_download_completion * realm_dart_sync_session_wait_for_upload_completion * realm_dart_sync_session_register_progress_notifier * realm_dart_sync_session_register_connection_state_change_callback --- src/CMakeLists.txt | 8 ++- src/sync_session.cpp | 128 +++++++++++++++++++++++++++++++++++++++++++ src/sync_session.h | 72 ++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 src/sync_session.cpp create mode 100644 src/sync_session.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c26cae3e7..722379b31 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,9 +8,10 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp - subscription_set.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp + sync_session.cpp ) set(HEADERS @@ -18,6 +19,7 @@ set(HEADERS realm_dart_scheduler.h realm-core/src/realm.h subscription_set.h + sync_session.h ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) diff --git a/src/sync_session.cpp b/src/sync_session.cpp new file mode 100644 index 000000000..4cd0ea4ac --- /dev/null +++ b/src/sync_session.cpp @@ -0,0 +1,128 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#include + +#include "sync_session.h" +#include "event_loop_dispatcher.hpp" + +namespace realm::c_api { +namespace _1 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_sync_error_code_t* error) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(error); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API void realm_dart_sync_session_wait_for_download_completion(realm_sync_session_t* session, + realm_sync_download_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + realm_sync_session_wait_for_download_completion(session, _callback, u, _userdata_free); +} + +RLM_API void realm_dart_sync_session_wait_for_upload_completion(realm_sync_session_t* session, + realm_sync_upload_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + realm_sync_session_wait_for_upload_completion(session, _callback, u, _userdata_free); +} + +} // anonymous namespace + +namespace _2 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, uint64_t transferred_bytes, uint64_t total_bytes) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(transferred_bytes, total_bytes); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API uint64_t realm_dart_sync_session_register_progress_notifier(realm_sync_session_t* session, + realm_sync_progress_func_t callback, + realm_sync_progress_direction_e direction, + bool is_streaming, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_session_register_progress_notifier(session, _callback, direction, is_streaming, u, _userdata_free); +} + +} // anonymous namespace + +namespace _3 { + +using FreeT = std::function; +using CallbackT = std::function; +using UserdataT = std::tuple; + +void _callback(void* userdata, realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state) { + auto u = reinterpret_cast(userdata); + std::get<0>(*u)(old_state, new_state); +} + +void _userdata_free(void* userdata) { + auto u = reinterpret_cast(userdata); + std::get<1>(*u)(); + delete u; +} + +RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, + realm_sync_connection_state_changed_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) noexcept +{ + auto u = new UserdataT(std::bind(util::EventLoopDispatcher{ *scheduler, callback }, userdata, std::placeholders::_1, std::placeholders::_2), + std::bind(util::EventLoopDispatcher{ *scheduler, userdata_free }, userdata)); + return realm_sync_session_register_connection_state_change_callback(session, _callback, u, _userdata_free); +} + +} // anonymous namespace + +} // namespace realm::c_api diff --git a/src/sync_session.h b/src/sync_session.h new file mode 100644 index 000000000..62bcbcdce --- /dev/null +++ b/src/sync_session.h @@ -0,0 +1,72 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +#ifndef REALM_DART_SYNC_SESSION_H +#define REALM_DART_SYNC_SESSION_H + +#include "realm.h" + +/** + * Register a callback that will be invoked when all pending downloads have completed. + */ +RLM_API void realm_dart_sync_session_wait_for_download_completion(realm_sync_session_t* session, + realm_sync_download_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked when all pending uploads have completed. + */ +RLM_API void realm_dart_sync_session_wait_for_upload_completion(realm_sync_session_t* session, + realm_sync_upload_completion_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked every time the session reports progress. + * + * @param is_streaming If true, then the notifier will be called forever, and will + * always contain the most up-to-date number of downloadable or uploadable bytes. + * Otherwise, the number of downloaded or uploaded bytes will always be reported + * relative to the number of downloadable or uploadable bytes at the point in time + * when the notifier was registered. + * @return A token value that can be used to unregister the notifier. + */ +RLM_API uint64_t realm_dart_sync_session_register_progress_notifier(realm_sync_session_t* session, + realm_sync_progress_func_t callback, + realm_sync_progress_direction_e direction, + bool is_streaming, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + +/** + * Register a callback that will be invoked every time the session's connection state changes. + * + * @return A token value that can be used to unregister the callback. + */ +RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callback(realm_sync_session_t* session, + realm_sync_connection_state_changed_func_t callback, + void* userdata, + realm_free_userdata_func_t userdata_free, + realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; + + +#endif // REALM_DART_SYNC_SESSION_H \ No newline at end of file From 757322fdc4246ecdf2baee423316d467d9712930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kasper=20Overg=C3=A5rd=20Nielsen?= Date: Wed, 11 May 2022 20:14:21 +0200 Subject: [PATCH 062/122] Update bindings --- ffigen/config.yaml | 2 + ffigen/sync_session.h | 1 + lib/src/native/realm_bindings.dart | 159 +++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 120000 ffigen/sync_session.h diff --git a/ffigen/config.yaml b/ffigen/config.yaml index 22243f7d8..b0e1a9d72 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -8,12 +8,14 @@ headers: - 'realm_dart_scheduler.h' - 'realm_android_platform.h' - 'subscription_set.h' + - 'sync_session.h' include-directives: #generate only for these headers - 'realm.h' - 'realm_dart.h' - 'realm_dart_scheduler.h' - 'realm_android_platform.h' - 'subscription_set.h' + - 'sync_session.h' compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' diff --git a/ffigen/sync_session.h b/ffigen/sync_session.h new file mode 120000 index 000000000..2970acaff --- /dev/null +++ b/ffigen/sync_session.h @@ -0,0 +1 @@ +../src/sync_session.h \ No newline at end of file diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 6e70c8257..163e1aa89 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -8722,6 +8722,165 @@ class RealmLibrary { ffi.Pointer, realm_free_userdata_func_t, ffi.Pointer)>(); + + /// Register a callback that will be invoked when all pending downloads have completed. + void realm_dart_sync_session_wait_for_download_completion( + ffi.Pointer session, + realm_sync_download_completion_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_wait_for_download_completion( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_wait_for_download_completionPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_download_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_wait_for_download_completion'); + late final _realm_dart_sync_session_wait_for_download_completion = + _realm_dart_sync_session_wait_for_download_completionPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_download_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked when all pending uploads have completed. + void realm_dart_sync_session_wait_for_upload_completion( + ffi.Pointer session, + realm_sync_upload_completion_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_wait_for_upload_completion( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_wait_for_upload_completionPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + realm_sync_upload_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_wait_for_upload_completion'); + late final _realm_dart_sync_session_wait_for_upload_completion = + _realm_dart_sync_session_wait_for_upload_completionPtr.asFunction< + void Function( + ffi.Pointer, + realm_sync_upload_completion_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked every time the session reports progress. + /// + /// @param is_streaming If true, then the notifier will be called forever, and will + /// always contain the most up-to-date number of downloadable or uploadable bytes. + /// Otherwise, the number of downloaded or uploaded bytes will always be reported + /// relative to the number of downloadable or uploadable bytes at the point in time + /// when the notifier was registered. + /// @return A token value that can be used to unregister the notifier. + int realm_dart_sync_session_register_progress_notifier( + ffi.Pointer session, + realm_sync_progress_func_t callback, + int direction, + bool is_streaming, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_register_progress_notifier( + session, + callback, + direction, + is_streaming ? 1 : 0, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_register_progress_notifierPtr = _lookup< + ffi.NativeFunction< + ffi.Uint64 Function( + ffi.Pointer, + realm_sync_progress_func_t, + ffi.Int32, + ffi.Uint8, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_register_progress_notifier'); + late final _realm_dart_sync_session_register_progress_notifier = + _realm_dart_sync_session_register_progress_notifierPtr.asFunction< + int Function( + ffi.Pointer, + realm_sync_progress_func_t, + int, + int, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); + + /// Register a callback that will be invoked every time the session's connection state changes. + /// + /// @return A token value that can be used to unregiser the callback. + int realm_dart_sync_session_register_connection_state_change_callback( + ffi.Pointer session, + realm_sync_connection_state_changed_func_t callback, + ffi.Pointer userdata, + realm_free_userdata_func_t userdata_free, + ffi.Pointer scheduler, + ) { + return _realm_dart_sync_session_register_connection_state_change_callback( + session, + callback, + userdata, + userdata_free, + scheduler, + ); + } + + late final _realm_dart_sync_session_register_connection_state_change_callbackPtr = + _lookup< + ffi.NativeFunction< + ffi.Uint64 Function( + ffi.Pointer, + realm_sync_connection_state_changed_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>>( + 'realm_dart_sync_session_register_connection_state_change_callback'); + late final _realm_dart_sync_session_register_connection_state_change_callback = + _realm_dart_sync_session_register_connection_state_change_callbackPtr + .asFunction< + int Function( + ffi.Pointer, + realm_sync_connection_state_changed_func_t, + ffi.Pointer, + realm_free_userdata_func_t, + ffi.Pointer)>(); } class shared_realm extends ffi.Opaque {} From db3b075f370acfb5d3aca2b50d95546d0e60f733 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 11 May 2022 23:24:02 +0200 Subject: [PATCH 063/122] Wire up some of the session API --- .vscode/settings.json | 4 + common/lib/src/realm_types.dart | 32 +++++- ffigen/config.yaml | 4 +- lib/src/app.dart | 13 ++- lib/src/native/realm_core.dart | 117 +++++++++++++++++++++ lib/src/realm_class.dart | 25 ++++- lib/src/session.dart | 177 ++++++++++++++++++++++++++++++++ lib/src/user.dart | 8 +- test/app_test.dart | 5 +- test/session_test.dart | 124 ++++++++++++++++++++++ test/test.dart | 38 +++++-- 11 files changed, 524 insertions(+), 23 deletions(-) create mode 100644 lib/src/session.dart create mode 100644 test/session_test.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 03c32b502..f4741ea32 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -19,4 +19,8 @@ "visibility": "hidden" } }, + "files.associations": { + "filesystem": "cpp", + "*.ipp": "cpp" + }, } \ No newline at end of file diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 903c53716..584128582 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -63,7 +63,7 @@ enum RealmCollectionType { class RealmError extends Error { final String? message; RealmError(String this.message); - + @override String toString() => "Realm error : $message"; } @@ -79,6 +79,36 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } +/// Thrown or reporeted if an error occurs during synchronization +/// {@category Sync} +class SyncError extends RealmError { + /// The code of the error + final int code; // TODO: this should be an enum + + /// The category of the error + final SyncErrorCategory category; + + SyncError(String message, this.category, this.code) : super(message); +} + +/// The category of a [SyncError]. +enum SyncErrorCategory { + /// The error originated from the client + client, + + /// The error originated from the connection + connection, + + /// The error originated from the session + session, + + /// Another low-level system error occurred + system, + + /// The category is unknown + unknown, +} + /// @nodoc class Decimal128 {} // TODO! diff --git a/ffigen/config.yaml b/ffigen/config.yaml index b0e1a9d72..1c372a084 100644 --- a/ffigen/config.yaml +++ b/ffigen/config.yaml @@ -1,4 +1,4 @@ -# Usage: dart run ffigen --config config.yaml +# Usage: dart run ffigen --config config.yaml name: RealmLibrary output: "../lib/src/native/realm_bindings.dart" headers: @@ -16,7 +16,7 @@ headers: - 'realm_android_platform.h' - 'subscription_set.h' - 'sync_session.h' -compiler-opts: +compiler-opts: - '-DRLM_NO_ANON_UNIONS' - '-DFFI_GEN' typedef-map: diff --git a/lib/src/app.dart b/lib/src/app.dart index 86491417a..ef9e1abb4 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -148,15 +148,16 @@ class AppConfiguration { /// {@category Application} class App { final AppHandle _handle; - final AppConfiguration configuration; /// Create an app with a particular [AppConfiguration] - App(this.configuration) : _handle = realmCore.getApp(configuration); + App(AppConfiguration configuration) : this._(realmCore.getApp(configuration)); + + App._(this._handle); /// Logs in a user with the given credentials. Future logIn(Credentials credentials) async { var userHandle = await realmCore.logIn(this, credentials); - return UserInternal.create(this, userHandle); + return UserInternal.create(userHandle, app: this); } /// Gets the currently logged in [User]. If none exists, `null` is returned. @@ -165,12 +166,12 @@ class App { if (userHandle == null) { return null; } - return UserInternal.create(this, userHandle); + return UserInternal.create(userHandle, app: this); } /// Gets all currently logged in users. Iterable get users { - return realmCore.getUsers(this).map((handle) => UserInternal.create(this, handle)); + return realmCore.getUsers(this).map((handle) => UserInternal.create(handle, app: 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. @@ -203,4 +204,6 @@ enum MetadataPersistenceMode { /// @nodoc extension AppInternal on App { AppHandle get handle => _handle; + + static App create(AppHandle handle) => App._(handle); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 1ea2b584c..b4823e310 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -35,6 +35,7 @@ import '../realm_object.dart'; import '../results.dart'; import '../subscription.dart'; import '../user.dart'; +import '../session.dart'; import 'realm_bindings.dart'; late RealmLibrary _realmLib; @@ -1345,6 +1346,11 @@ class _RealmCore { return userId; } + AppHandle userGetApp(UserHandle userHandle) { + // TODO: we don't have an API to get the app for a user - https://github.com/realm/realm-core/issues/5478 + return AppHandle._(nullptr); + } + List userGetIdentities(User user) { return using((arena) { //TODO: This approach is prone to race conditions. Fix this once Core changes how count is retrieved. @@ -1386,6 +1392,106 @@ class _RealmCore { final dynamic profileData = jsonDecode(data.cast().toRealmDartString(freeNativeMemory: true)!); return UserProfile(profileData as Map); } + + SessionHandle realmGetSession(Realm realm) { + return SessionHandle._(_realmLib.invokeGetPointer(() => _realmLib.realm_sync_session_get(realm.handle._pointer))); + } + + 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 + 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); + } + + int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { + final isStreaming = mode == ProgressMode.reportIndefinitely; + // TODO: this should use the dart version of this method + return _realmLib.realm_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, + isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr); + } + + void sessionUnregisterProgressNotifier(Session session, int token) { + _realmLib.realm_sync_session_unregister_progress_notifier(session.handle._pointer, token); + } + + static void on_sync_progress(Pointer userdata, int transferred, int transferable) { + final SessionProgressNotificationsController? controller = userdata.toObject(isPersistent: true); + if (controller == null) { + return; + } + + controller.onProgress(transferred, transferable); + } + + Future sessionWaitForUpload(Session session) { + final completer = Completer(); + _realmLib.realm_dart_sync_session_wait_for_upload_completion( + session.handle._pointer, + Pointer.fromFunction(_waitCompletionCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + session.scheduler.handle._pointer, + ); + return completer.future; + } + + Future sessionWaitForDownload(Session session) { + final completer = Completer(); + _realmLib.realm_dart_sync_session_wait_for_download_completion( + session.handle._pointer, + Pointer.fromFunction(_waitCompletionCallback), + completer.toPersistentHandle(), + _deletePersistentHandleFuncPtr, + session.scheduler.handle._pointer, + ); + return completer.future; + } + + static void _waitCompletionCallback(Pointer userdata, Pointer errorCode) { + final completer = userdata.toObject>(isPersistent: true); + if (completer == null) { + return; + } + + if (errorCode != nullptr) { + completer.completeError(errorCode.toSyncError()); + } else { + completer.complete(); + } + } } class LastError { @@ -1527,6 +1633,10 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { Pointer get _mutablePointer => super._pointer.cast(); } +class SessionHandle extends ReleasableHandle { + SessionHandle._(Pointer pointer) : super(pointer, 24); // TODO: what is the size? +} + extension on List { Pointer toInt8Ptr(Allocator allocator) { return toUint8Ptr(allocator).cast(); @@ -1719,6 +1829,13 @@ extension on Pointer { } } +extension on Pointer { + SyncError toSyncError() { + final message = ref.message.cast().toRealmDartString()!; + return SyncError(message, SyncErrorCategory.values[ref.category], ref.value); + } +} + extension on Object { Pointer toWeakHandle() { return _realmLib.object_to_weak_handle(this); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d8d7e7c97..876a33ae8 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -29,6 +29,7 @@ import 'native/realm_core.dart'; import 'realm_object.dart'; import 'results.dart'; import 'subscription.dart'; +import 'session.dart'; export 'package:realm_common/realm_common.dart' show @@ -37,6 +38,8 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, + SyncError, + SyncErrorCategory, RealmModel, RealmUnsupportedSetError, RealmStateError, @@ -65,6 +68,7 @@ export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState; +export 'session.dart' show Session, SessionState, ConnectionState; /// A [Realm] instance represents a `Realm` database. /// @@ -221,6 +225,12 @@ class Realm { /// All [RealmObject]s and `Realm ` collections are invalidated and can not be used. /// This method will not throw if called multiple times. void close() { + _syncSession?.handle.release(); + _syncSession = null; + + _subscriptions?.handle.release(); + _subscriptions = null; + realmCore.closeRealm(this); _scheduler.stop(); } @@ -275,7 +285,7 @@ class Realm { SubscriptionSet? _subscriptions; - /// The active [subscriptions] for this [Realm] + /// The active [SubscriptionSet] for this [Realm] SubscriptionSet get subscriptions { if (config is! FlexibleSyncConfiguration) throw RealmError('subscriptions is only valid on Realms opened with a FlexibleSyncConfiguration'); _subscriptions ??= SubscriptionSetInternal.create(this, realmCore.getSubscriptions(this)); @@ -283,6 +293,19 @@ class Realm { return _subscriptions!; } + Session? _syncSession; + + /// The [Session] for this [Realm]. The sync session is responsible for two-way synchronization + /// with MongoDB Atlas. If the [Realm] was is not synchronized, accessing this property will throw. + Session get syncSession { + if (config is! FlexibleSyncConfiguration) { + throw RealmError('session is only valid on synchronized Realms (i.e. opened with FlexibleSyncConfiguration)'); + } + + _syncSession ??= SessionInternal.create(realmCore.realmGetSession(this), scheduler); + return _syncSession!; + } + @override // ignore: hash_and_equals bool operator ==(Object other) { diff --git a/lib/src/session.dart b/lib/src/session.dart new file mode 100644 index 000000000..30ec4d064 --- /dev/null +++ b/lib/src/session.dart @@ -0,0 +1,177 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:async'; + +import '../realm.dart'; +import 'native/realm_core.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 +/// server. Sessions are always created by the SDK and vended out through various +/// APIs. The lifespans of sessions associated with Realms are managed automatically. +/// {@category Sync} +class Session { + final SessionHandle _handle; + + // This is very unpleasant, but the way we dispatch callbacks from native into + // dart requires us to pass down the scheudler. + final Scheduler _scheduler; + + /// The on-disk path of the file backing the [Realm] this [Session] represents + String get path => realmCore.sessionGetPath(this); + + /// 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); + + /// 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); + + /// The [User] that owns the [Realm] this [Session] is synchronizing. + User get user => UserInternal.create(realmCore.sessionGetUser(this)); + + Session._(this._handle, this._scheduler); + + /// 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); + + /// 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); + + /// Waits for the [Session] to finish all pending uploads. + Future waitForUpload() => realmCore.sessionWaitForUpload(this); + + /// Waits for the [Session] to finish all pending downloads. + Future waitForDownload() => realmCore.sessionWaitForDownload(this); + + /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. + Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { + final controller = SessionProgressNotificationsController(this, direction, mode); + return controller.createStream(); + } +} + +/// The current state of a [Session] object +enum SessionState { + /// The session is connected to the MongoDB Realm server and is actively transferring data. + active, + + /// The session is not currently communicating with the server. + inactive, +} + +/// The current connection state of a [Session] object +enum ConnectionState { + /// The session is disconnected from the MongoDB Realm server. + disconnected, + + /// The session is connecting to the MongoDB Realm server. + connecting, + + /// The session is connected to the MongoDB Realm server. + connected, +} + +/// The transfer direction (upload or download) tracked by a given progress notification subscription. +enum ProgressDirection { + /// Monitors upload progress. + upload, + + /// Monitors download progress. + download +} + +/// The desired behavior of a progress notification subscription. +enum ProgressMode { + /// The callback will be called forever, or until it is unregistered by disposing the subscription token. + /// Notifications will always report the latest number of transferred bytes, and the most up-to-date number of + /// total transferable bytes. + reportIndefinitely, + + /// The callback will, upon registration, store the total number of bytes to be transferred. When invoked, it will + /// always report the most up-to-date number of transferable bytes out of that original number of transferable bytes. + /// When the number of transferred bytes reaches or exceeds the number of transferable bytes, the callback will + /// be unregistered. + forCurrentlyOutstandingWork, +} + +/// A type containing information about the progress state at a given instant. +class SyncProgress { + /// The number of bytes that have been transferred since subscribing for progress notifications. + final int transferredBytes; + + /// The total number of bytes that have to be transferred since subscribing for progress notifications. + /// The difference between that number and [transferredBytes] gives you the number of bytes not yet + /// transferred. If the difference is 0, then all changes at the instant the callback fires have been + /// successfully transferred. + final int transferableBytes; + + SyncProgress._(this.transferredBytes, this.transferableBytes); +} + +extension SessionInternal on Session { + static Session create(SessionHandle handle, Scheduler scheduler) => Session._(handle, scheduler); + + SessionHandle get handle => _handle; + + Scheduler get scheduler => _scheduler; +} + +/// @nodoc +class SessionProgressNotificationsController { + final Session _session; + final ProgressDirection _direction; + final ProgressMode _mode; + + late int? _token; + late final StreamController _streamController; + + SessionProgressNotificationsController(this._session, this._direction, this._mode); + + Stream createStream() { + _streamController = StreamController.broadcast(onListen: _start, onCancel: _stop); + return _streamController.stream; + } + + void onProgress(int transferredBytes, int transferableBytes) { + _streamController.add(SyncProgress._(transferredBytes, transferableBytes)); + } + + void _start() { + if (_token != null) { + throw RealmStateError("Session progress subscription already started"); + } + + _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this); + } + + void _stop() { + if (_token == null) { + return; + } + + realmCore.sessionUnregisterProgressNotifier(_session, _token!); + } +} diff --git a/lib/src/user.dart b/lib/src/user.dart index 133f87ce4..cff8a1201 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -107,7 +107,7 @@ class User { /// ``` Future linkCredentials(Credentials credentials) async { final userHandle = await realmCore.userLinkCredentials(app, this, credentials); - return UserInternal.create(app, userHandle); + return UserInternal.create(userHandle, app: app); } @override @@ -188,5 +188,9 @@ extension UserIdentityInternal on UserIdentity { extension UserInternal on User { UserHandle get handle => _handle; - static User create(App app, UserHandle handle) => User._(app, handle); + static User create(UserHandle handle, {App? app}) { + app ??= AppInternal.create(realmCore.userGetApp(handle)); + + return User._(app, handle); + } } diff --git a/test/app_test.dart b/test/app_test.dart index dae88b954..5c6803b59 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -101,8 +101,7 @@ test('AppConfiguration can be created with defaults', () { test('App can be created', () async { final configuration = AppConfiguration(generateRandomString(10)); - final app = App(configuration); - expect(app.configuration, configuration); + App(configuration); }); baasTest('App log in', (configuration) async { @@ -172,7 +171,7 @@ test('AppConfiguration can be created with defaults', () { final user1 = await app.logIn(Credentials.anonymous()); expect(app.currentUser, user1); - + final user2 = await app.logIn(Credentials.emailPassword(testUsername, testPassword)); expect(app.currentUser, user2); diff --git a/test/session_test.dart b/test/session_test.dart new file mode 100644 index 000000000..1ac571182 --- /dev/null +++ b/test/session_test.dart @@ -0,0 +1,124 @@ +//////////////////////////////////////////////////////////////////////////////// +// +// Copyright 2022 Realm Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +//////////////////////////////////////////////////////////////////////////////// + +import 'dart:io'; + +import 'package:test/test.dart' hide test, throws; +import '../lib/realm.dart'; +import 'test.dart'; + +Future main([List? args]) async { + print("Current PID $pid"); + + await setupTests(args); + + test('Realm.syncSession throws on wrong configuration', () { + final config = Configuration.local([Task.schema]); + final realm = getRealm(config); + expect(() => realm.syncSession, throws()); + }); + + baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + expect(realm.syncSession, isNotNull); + expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession, realm.syncSession); + }); + + baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + expect(realm.syncSession, isNotNull); + expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession, realm.syncSession); + }); + + baasTest('SyncSession.user returns a valid user', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema]); + final realm = getRealm(config); + + expect(realm.syncSession.user, user); + expect(realm.syncSession.user.id, user.id); + }); + + baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema]); + + // Don't use getRealm because we want the Realm to survive + final realm = Realm(config); + + expect(realm.syncSession, isNotNull); + }, skip: 'crashes'); + + Future _validateSessionStates(Session session, {SessionState? sessionState, ConnectionState? connectionState}) async { + if (sessionState != null) { + expect(session.state.name, sessionState.name); + } + + if (connectionState != null) { + // The connection requires a bit of time to update its state + await Future.delayed(Duration(milliseconds: 100)); + expect(session.connectionState.name, connectionState.name); + } + } + + baasTest('SyncSession.pause/resume', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive, connectionState: ConnectionState.disconnected); + + realm.syncSession.resume(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + }); + + baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + + // This should not do anything + realm.syncSession.pause(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + }); + + baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { + final realm = await getIntegrationRealm([Task.schema]); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + + realm.syncSession.resume(); + realm.syncSession.resume(); + + await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + }); +} diff --git a/test/test.dart b/test/test.dart index 9100b6782..d59f7b08b 100644 --- a/test/test.dart +++ b/test/test.dart @@ -256,18 +256,38 @@ Future baasTest( } test(name, () async { - final app = baasApps[appName.name] ?? - baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); - final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); - final appConfig = AppConfiguration( - app.clientAppId, - baseUrl: url, - baseFilePath: temporaryDir, - ); - return await testFunction(appConfig); + final config = await getAppConfig(appName: appName); + return await testFunction(config); }, skip: skip); } +Future getAppConfig({AppNames appName = AppNames.flexible}) async { + final app = baasApps[appName.name] ?? + baasApps.values.firstWhere((element) => element.name == BaasClient.defaultAppName, orElse: () => throw RealmError("No BAAS apps")); + + final temporaryDir = await Directory.systemTemp.createTemp('realm_test_'); + return AppConfiguration( + app.clientAppId, + baseUrl: Uri.parse(Platform.environment['BAAS_URL']!), + baseFilePath: temporaryDir, + ); +} + +Future getIntegrationUser(App app) async { + final email = 'realm_tests_do_autoverify_${generateRandomString(10)}@realm.io'; + final password = 'password'; + await app.emailPasswordAuthProvider.registerUser(email, password); + + return await loginWithRetry(app, Credentials.emailPassword(email, password)); +} + +Future getIntegrationRealm(List schemas, {App? app}) async { + app ??= App(await getAppConfig()); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, schemas); + return getRealm(config); +} + Future loginWithRetry(App app, Credentials credentials, {int retryCount = 3}) async { try { return await app.logIn(credentials); From f6c3659d0ac4efca8faff740e7723defa15a1787 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Thu, 12 May 2022 01:18:59 +0200 Subject: [PATCH 064/122] Use the dart trampoline --- lib/src/native/realm_core.dart | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b4823e310..137274432 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1438,9 +1438,8 @@ class _RealmCore { int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { final isStreaming = mode == ProgressMode.reportIndefinitely; - // TODO: this should use the dart version of this method - return _realmLib.realm_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, - isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr); + return _realmLib.realm_dart_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, + isStreaming, controller.toPersistentHandle(), _deletePersistentHandleFuncPtr, session.scheduler.handle._pointer); } void sessionUnregisterProgressNotifier(Session session, int token) { From ed1e21e1521592c9190919f1ee77abdb46bb5983 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Mon, 16 May 2022 23:57:58 +0200 Subject: [PATCH 065/122] use correct config ctor --- test/session_test.dart | 4 ++-- test/test.dart | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/test/session_test.dart b/test/session_test.dart index 1ac571182..550cf4e6e 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -52,7 +52,7 @@ Future main([List? args]) async { baasTest('SyncSession.user returns a valid user', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.sync(user, [Task.schema]); final realm = getRealm(config); expect(realm.syncSession.user, user); @@ -62,7 +62,7 @@ Future main([List? args]) async { baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema]); + final config = Configuration.sync(user, [Task.schema]); // Don't use getRealm because we want the Realm to survive final realm = Realm(config); diff --git a/test/test.dart b/test/test.dart index d59f7b08b..3c3ce5a60 100644 --- a/test/test.dart +++ b/test/test.dart @@ -102,7 +102,6 @@ class _Schedule { final tasks = <_Task>[]; } - String? testName; final baasApps = {}; final _openRealms = Queue(); @@ -284,7 +283,7 @@ Future getIntegrationUser(App app) async { Future getIntegrationRealm(List schemas, {App? app}) async { app ??= App(await getAppConfig()); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, schemas); + final config = Configuration.sync(user, schemas); return getRealm(config); } From 80a946561523cb1819b41a7b10ee8fa2f359cd31 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 17 May 2022 02:03:37 +0200 Subject: [PATCH 066/122] Add more tests --- lib/src/cli/deployapps/baas_client.dart | 2 +- lib/src/realm_class.dart | 2 +- lib/src/session.dart | 6 +- test/session_test.dart | 185 +++++++++++++++++++++++- test/test.dart | 34 ++++- test/test.g.dart | 101 +++++++++++++ 6 files changed, 319 insertions(+), 11 deletions(-) diff --git a/lib/src/cli/deployapps/baas_client.dart b/lib/src/cli/deployapps/baas_client.dart index 819117ae4..b18473828 100644 --- a/lib/src/cli/deployapps/baas_client.dart +++ b/lib/src/cli/deployapps/baas_client.dart @@ -182,7 +182,7 @@ class BaasClient { "flexible_sync": { "state": "enabled", "database_name": "flexible_sync_data", - "queryable_fields_names": ["TODO"], + "queryable_fields_names": ["differentiator"], "permissions": { "rules": {}, "defaultRoles": [ diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 876a33ae8..05a073fbc 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -68,7 +68,7 @@ export 'realm_property.dart'; export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState; -export 'session.dart' show Session, SessionState, ConnectionState; +export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress; /// A [Realm] instance represents a `Realm` database. /// diff --git a/lib/src/session.dart b/lib/src/session.dart index 30ec4d064..f59f9addb 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -145,7 +145,7 @@ class SessionProgressNotificationsController { final ProgressDirection _direction; final ProgressMode _mode; - late int? _token; + int? _token; late final StreamController _streamController; SessionProgressNotificationsController(this._session, this._direction, this._mode); @@ -157,6 +157,10 @@ class SessionProgressNotificationsController { void onProgress(int transferredBytes, int transferableBytes) { _streamController.add(SyncProgress._(transferredBytes, transferableBytes)); + + if (transferredBytes >= transferableBytes && _mode == ProgressMode.forCurrentlyOutstandingWork) { + _streamController.close(); + } } void _start() { diff --git a/test/session_test.dart b/test/session_test.dart index 550cf4e6e..3a3af54e3 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -16,6 +16,7 @@ // //////////////////////////////////////////////////////////////////////////////// +import 'dart:async'; import 'dart:io'; import 'package:test/test.dart' hide test, throws; @@ -34,7 +35,7 @@ Future main([List? args]) async { }); baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); expect(realm.syncSession.path, realm.config.path); @@ -42,7 +43,7 @@ Future main([List? args]) async { }); baasTest('Realm.syncSession returns on FLX configuration', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); expect(realm.syncSession.path, realm.config.path); @@ -83,7 +84,7 @@ Future main([List? args]) async { } baasTest('SyncSession.pause/resume', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); @@ -97,7 +98,7 @@ Future main([List? args]) async { }); baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); @@ -112,7 +113,7 @@ Future main([List? args]) async { }); baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { - final realm = await getIntegrationRealm([Task.schema]); + final realm = await getIntegrationRealm(); await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); @@ -121,4 +122,178 @@ Future main([List? args]) async { await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); }); + + baasTest('SyncSession.waitForUpload with no changes', (configuration) async { + final realm = await getIntegrationRealm(); + + await realm.syncSession.waitForUpload(); + + // Call it multiple times to make sure it doesn't throw + await realm.syncSession.waitForUpload(); + }); + + baasTest('SyncSession.waitForDownload with no changes', (configuration) async { + final realm = await getIntegrationRealm(); + + await realm.syncSession.waitForDownload(); + + // Call it multiple times to make sure it doesn't throw + await realm.syncSession.waitForDownload(); + }); + + baasTest('SyncSesison.waitForUpload with changes', (configuration) async { + final differentiator = ObjectId(); + + final realmA = await getIntegrationRealm(differentiator: differentiator); + final realmB = await getIntegrationRealm(differentiator: differentiator, path: generateRandomRealmPath()); + + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: 'abc')); + }); + + await realmA.syncSession.waitForUpload(); + await realmB.syncSession.waitForDownload(); + + expect(realmA.all().map((e) => e.stringProp), realmB.all().map((e) => e.stringProp)); + + realmB.write(() { + realmB.add(NullableTypes(ObjectId(), differentiator, stringProp: 'def')); + }); + + await realmB.syncSession.waitForUpload(); + await realmA.syncSession.waitForDownload(); + + expect(realmA.all().map((e) => e.stringProp), realmB.all().map((e) => e.stringProp)); + }); + + StreamProgressData subscribeToProgress(Realm realm, ProgressDirection direction, ProgressMode mode) { + final data = StreamProgressData(); + final stream = realm.syncSession.getProgressStream(direction, mode); + data.subscription = stream.listen((event) { + expect(event.transferredBytes, greaterThanOrEqualTo(data.transferredBytes)); + if (data.transferableBytes != 0) { + // We need to wait for the first event to store the total bytes we expect. + if (mode == ProgressMode.forCurrentlyOutstandingWork) { + // Transferable should not change after the first event + expect(event.transferableBytes, data.transferableBytes); + } else { + // For indefinite progress, we expect the transferable bytes to not decrease + expect(event.transferableBytes, greaterThanOrEqualTo(data.transferableBytes)); + } + } + + data.transferredBytes = event.transferredBytes; + data.transferableBytes = event.transferableBytes; + data.callbacksInvoked++; + }); + + data.subscription.onDone(() { + data.doneInvoked = true; + }); + + return data; + } + + Future validateData(StreamProgressData data, {bool expectDone = false}) async { + // Wait a little since the last event is sent asynchronously + await Future.delayed(Duration(milliseconds: 100)); + + expect(data.callbacksInvoked, greaterThan(0)); + expect(data.transferableBytes, greaterThan(0)); + expect(data.transferredBytes, greaterThan(0)); + if (expectDone) { + expect(data.transferredBytes, data.transferableBytes); + } else { + expect(data.transferredBytes, lessThanOrEqualTo(data.transferableBytes)); + } + expect(data.doneInvoked, expectDone); + } + + baasTest('SyncSession.getProgressStream forCurrentlyOutstandingWork', (configuration) async { + final differentiator = ObjectId(); + final realmA = await getIntegrationRealm(differentiator: differentiator); + final realmB = await getIntegrationRealm(differentiator: differentiator, path: generateRandomRealmPath()); + + for (var i = 0; i < 10; i++) { + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: generateRandomString(50))); + }); + } + + final uploadData = subscribeToProgress(realmA, ProgressDirection.upload, ProgressMode.forCurrentlyOutstandingWork); + + await realmA.syncSession.waitForUpload(); + + // Subscribe immediately after the upload to ensure we get the entire upload message as progress notifications + final downloadData = subscribeToProgress(realmB, ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); + + await validateData(uploadData, expectDone: true); + + await realmB.syncSession.waitForDownload(); + + await validateData(downloadData, expectDone: true); + + await uploadData.subscription.cancel(); + await downloadData.subscription.cancel(); + }); + + baasTest('SyncSession.getProgressStream reportIndefinitely', (configuration) async { + final differentiator = ObjectId(); + final realmA = await getIntegrationRealm(differentiator: differentiator); + final realmB = await getIntegrationRealm(differentiator: differentiator, path: generateRandomRealmPath()); + + for (var i = 0; i < 10; i++) { + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: generateRandomString(50))); + }); + } + + final uploadData = subscribeToProgress(realmA, ProgressDirection.upload, ProgressMode.reportIndefinitely); + final downloadData = subscribeToProgress(realmB, ProgressDirection.download, ProgressMode.reportIndefinitely); + + await realmA.syncSession.waitForUpload(); + await validateData(uploadData); + + await realmB.syncSession.waitForDownload(); + await validateData(downloadData); + + // Snapshot the current state, then add a new object. We should receive more notifications + final uploadSnapshot = StreamProgressData.snapshot(uploadData); + final downloadSnapshot = StreamProgressData.snapshot(downloadData); + + realmA.write(() { + realmA.add(NullableTypes(ObjectId(), differentiator, stringProp: generateRandomString(50))); + }); + + await validateData(uploadData); + await validateData(downloadData); + + expect(uploadData.transferredBytes, greaterThan(uploadSnapshot.transferredBytes)); + expect(uploadData.transferableBytes, greaterThan(uploadSnapshot.transferableBytes)); + expect(uploadData.callbacksInvoked, greaterThan(uploadSnapshot.callbacksInvoked)); + + expect(downloadData.transferredBytes, greaterThan(downloadSnapshot.transferredBytes)); + expect(downloadData.transferableBytes, greaterThan(downloadSnapshot.transferableBytes)); + expect(downloadData.callbacksInvoked, greaterThan(downloadSnapshot.callbacksInvoked)); + + await uploadData.subscription.cancel(); + await downloadData.subscription.cancel(); + }); +} + +class StreamProgressData { + int transferredBytes; + int transferableBytes; + int callbacksInvoked; + bool doneInvoked; + late StreamSubscription subscription; + + StreamProgressData({this.transferableBytes = 0, this.transferredBytes = 0, this.callbacksInvoked = 0, this.doneInvoked = false}); + + StreamProgressData.snapshot(StreamProgressData other) + : this( + transferableBytes: other.transferableBytes, + callbacksInvoked: other.callbacksInvoked, + doneInvoked: other.doneInvoked, + transferredBytes: other.transferredBytes); } diff --git a/test/test.dart b/test/test.dart index 3c3ce5a60..087ee419a 100644 --- a/test/test.dart +++ b/test/test.dart @@ -102,6 +102,23 @@ class _Schedule { final tasks = <_Task>[]; } +@RealmModel() +class _NullableTypes { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; + + late ObjectId differentiator; + + late String? stringProp; + late bool? boolProp; + late DateTime? dateProp; + late double? doubleProp; + late ObjectId? objectIdProp; + late Uuid? uuidProp; + late int? intProp; +} + String? testName; final baasApps = {}; final _openRealms = Queue(); @@ -280,11 +297,22 @@ Future getIntegrationUser(App app) async { return await loginWithRetry(app, Credentials.emailPassword(email, password)); } -Future getIntegrationRealm(List schemas, {App? app}) async { +Future getIntegrationRealm({App? app, ObjectId? differentiator, String? path}) async { app ??= App(await getAppConfig()); final user = await getIntegrationUser(app); - final config = Configuration.sync(user, schemas); - return getRealm(config); + + // TODO: path will not be needed after https://github.com/realm/realm-dart/pull/574 + final config = Configuration.sync(user, [Task.schema, Schedule.schema, NullableTypes.schema], path: path); + final realm = getRealm(config); + if (differentiator != null) { + realm.subscriptions.update((mutableSubscriptions) { + mutableSubscriptions.add(realm.query('differentiator = \$0', [differentiator])); + }); + + await realm.subscriptions.waitForSynchronization(); + } + + return realm; } Future loginWithRetry(App app, Credentials credentials, {int retryCount = 3}) async { diff --git a/test/test.g.dart b/test/test.g.dart index 4033dffc9..0acc896b8 100644 --- a/test/test.g.dart +++ b/test/test.g.dart @@ -396,3 +396,104 @@ class Schedule extends _Schedule with RealmEntity, RealmObject { ]); } } + +class NullableTypes extends _NullableTypes with RealmEntity, RealmObject { + NullableTypes( + ObjectId id, + ObjectId differentiator, { + String? stringProp, + bool? boolProp, + DateTime? dateProp, + double? doubleProp, + ObjectId? objectIdProp, + Uuid? uuidProp, + int? intProp, + }) { + RealmObject.set(this, '_id', id); + RealmObject.set(this, 'differentiator', differentiator); + RealmObject.set(this, 'stringProp', stringProp); + RealmObject.set(this, 'boolProp', boolProp); + RealmObject.set(this, 'dateProp', dateProp); + RealmObject.set(this, 'doubleProp', doubleProp); + RealmObject.set(this, 'objectIdProp', objectIdProp); + RealmObject.set(this, 'uuidProp', uuidProp); + RealmObject.set(this, 'intProp', intProp); + } + + NullableTypes._(); + + @override + ObjectId get id => RealmObject.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => throw RealmUnsupportedSetError(); + + @override + ObjectId get differentiator => + RealmObject.get(this, 'differentiator') as ObjectId; + @override + set differentiator(ObjectId value) => + RealmObject.set(this, 'differentiator', value); + + @override + String? get stringProp => + RealmObject.get(this, 'stringProp') as String?; + @override + set stringProp(String? value) => RealmObject.set(this, 'stringProp', value); + + @override + bool? get boolProp => RealmObject.get(this, 'boolProp') as bool?; + @override + set boolProp(bool? value) => RealmObject.set(this, 'boolProp', value); + + @override + DateTime? get dateProp => + RealmObject.get(this, 'dateProp') as DateTime?; + @override + set dateProp(DateTime? value) => RealmObject.set(this, 'dateProp', value); + + @override + double? get doubleProp => + RealmObject.get(this, 'doubleProp') as double?; + @override + set doubleProp(double? value) => RealmObject.set(this, 'doubleProp', value); + + @override + ObjectId? get objectIdProp => + RealmObject.get(this, 'objectIdProp') as ObjectId?; + @override + set objectIdProp(ObjectId? value) => + RealmObject.set(this, 'objectIdProp', value); + + @override + Uuid? get uuidProp => RealmObject.get(this, 'uuidProp') as Uuid?; + @override + set uuidProp(Uuid? value) => RealmObject.set(this, 'uuidProp', value); + + @override + int? get intProp => RealmObject.get(this, 'intProp') as int?; + @override + set intProp(int? value) => RealmObject.set(this, 'intProp', value); + + @override + Stream> get changes => + RealmObject.getChanges(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(NullableTypes._); + return const SchemaObject(NullableTypes, 'NullableTypes', [ + SchemaProperty('_id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + SchemaProperty('differentiator', RealmPropertyType.objectid), + SchemaProperty('stringProp', RealmPropertyType.string, optional: true), + SchemaProperty('boolProp', RealmPropertyType.bool, optional: true), + SchemaProperty('dateProp', RealmPropertyType.timestamp, optional: true), + SchemaProperty('doubleProp', RealmPropertyType.double, optional: true), + SchemaProperty('objectIdProp', RealmPropertyType.objectid, + optional: true), + SchemaProperty('uuidProp', RealmPropertyType.uuid, optional: true), + SchemaProperty('intProp', RealmPropertyType.int, optional: true), + ]); + } +} From a8ee0f03e3c083c08192a3a3fb8aee920bd55e40 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Tue, 17 May 2022 02:15:05 +0200 Subject: [PATCH 067/122] formatting --- lib/src/realm_class.dart | 2 +- src/CMakeLists.txt | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 05a073fbc..69c33ef1e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -296,7 +296,7 @@ class Realm { Session? _syncSession; /// The [Session] for this [Realm]. The sync session is responsible for two-way synchronization - /// with MongoDB Atlas. If the [Realm] was is not synchronized, accessing this property will throw. + /// with MongoDB Atlas. If the [Realm] is not synchronized, accessing this property will throw. Session get syncSession { if (config is! FlexibleSyncConfiguration) { throw RealmError('session is only valid on synchronized Realms (i.e. opened with FlexibleSyncConfiguration)'); diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 722379b31..47ecb1bf9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -8,10 +8,10 @@ endif() add_subdirectory(dart-dl) set(SOURCES - realm_dart.cpp - realm_dart_scheduler.cpp - subscription_set.cpp - sync_session.cpp + realm_dart.cpp + realm_dart_scheduler.cpp + subscription_set.cpp + sync_session.cpp ) set(HEADERS From 230a0cd6f9781db6eea639e3f3827a44b69ed714 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 13:47:44 +0200 Subject: [PATCH 068/122] Address PR feedback --- lib/src/app.dart | 6 ++--- lib/src/native/realm_core.dart | 8 +++---- lib/src/session.dart | 5 ++-- lib/src/subscription.dart | 14 ++++-------- lib/src/user.dart | 18 +++++++-------- test/session_test.dart | 42 ++++++++++++++++++++-------------- 6 files changed, 48 insertions(+), 45 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index ef9e1abb4..a2d90fba9 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -157,7 +157,7 @@ class App { /// Logs in a user with the given credentials. Future logIn(Credentials credentials) async { var userHandle = await realmCore.logIn(this, credentials); - return UserInternal.create(userHandle, app: this); + return UserInternal.create(userHandle, this); } /// Gets the currently logged in [User]. If none exists, `null` is returned. @@ -166,12 +166,12 @@ class App { if (userHandle == null) { return null; } - return UserInternal.create(userHandle, app: this); + return UserInternal.create(userHandle, this); } /// Gets all currently logged in users. Iterable get users { - return realmCore.getUsers(this).map((handle) => UserInternal.create(handle, app: this)); + return realmCore.getUsers(this).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. diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 137274432..c4f34da78 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1583,7 +1583,7 @@ class RealmNotificationTokenHandle extends ReleasableHandle { - RealmCallbackTokenHandle._(Pointer pointer) : super(pointer, 32); + RealmCallbackTokenHandle._(Pointer pointer) : super(pointer, 24); } class RealmCollectionChangesHandle extends Handle { @@ -1619,11 +1619,11 @@ class UserHandle extends Handle { } class SubscriptionHandle extends Handle { - SubscriptionHandle._(Pointer pointer) : super(pointer, 24); + SubscriptionHandle._(Pointer pointer) : super(pointer, 184); } class SubscriptionSetHandle extends ReleasableHandle { - SubscriptionSetHandle._(Pointer pointer) : super(pointer, 24); + SubscriptionSetHandle._(Pointer pointer) : super(pointer, 128); } class MutableSubscriptionSetHandle extends SubscriptionSetHandle { @@ -1633,7 +1633,7 @@ class MutableSubscriptionSetHandle extends SubscriptionSetHandle { } class SessionHandle extends ReleasableHandle { - SessionHandle._(Pointer pointer) : super(pointer, 24); // TODO: what is the size? + SessionHandle._(Pointer pointer) : super(pointer, 24); } extension on List { diff --git a/lib/src/session.dart b/lib/src/session.dart index f59f9addb..08bf8d654 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -35,7 +35,7 @@ class Session { final Scheduler _scheduler; /// The on-disk path of the file backing the [Realm] this [Session] represents - String get path => realmCore.sessionGetPath(this); + String get realmPath => realmCore.sessionGetPath(this); /// 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 @@ -105,7 +105,7 @@ enum ProgressDirection { /// The desired behavior of a progress notification subscription. enum ProgressMode { - /// The callback will be called forever, or until it is unregistered by disposing the subscription token. + /// The callback will be called forever, or until it is unregistered by closing the `Stream`. /// Notifications will always report the latest number of transferred bytes, and the most up-to-date number of /// total transferable bytes. reportIndefinitely, @@ -177,5 +177,6 @@ class SessionProgressNotificationsController { } realmCore.sessionUnregisterProgressNotifier(_session, _token!); + _token = null; } } diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index bc5cdd70e..1112e232b 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -34,8 +34,6 @@ class Subscription { ObjectId get _id => realmCore.subscriptionId(this); /// Name of the [Subscription], if one was provided at creation time. - /// - /// Otherwise returns null. String? get name => realmCore.subscriptionName(this); /// Class name of objects the [Subscription] refers to. @@ -45,8 +43,8 @@ class Subscription { /// rather than the name of the generated Dart class. String get objectClassName => realmCore.subscriptionObjectClassName(this); - /// Query string that describes the [Subscription]. - /// + /// 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); @@ -62,7 +60,7 @@ class Subscription { if (identical(this, other)) return true; if (other is! Subscription) return false; // TODO: Don't work, issue with C-API - // return realmCore.subscriptionEquals(this, other); + // return realmCore.subscriptionEquals(this, other); return id == other.id; // <-- do this instead } } @@ -134,16 +132,12 @@ abstract class SubscriptionSet with IterableMixin { /// Finds an existing [Subscription] in this set by its query /// /// The [query] is represented by the corresponding [RealmResults] object. - /// - /// Returns null, if not found Subscription? find(RealmResults query) { final result = realmCore.findSubscriptionByResults(this, query); return result == null ? null : Subscription._(result); } /// Finds an existing [Subscription] in this set by name. - /// - /// Returns null, if not found Subscription? findByName(String name) { final result = realmCore.findSubscriptionByName(this, name); return result == null ? null : Subscription._(result); @@ -203,7 +197,7 @@ abstract class SubscriptionSet with IterableMixin { case SubscriptionSetState._bootstrapping: return SubscriptionSetState.pending; default: - return state; + return state; } } } diff --git a/lib/src/user.dart b/lib/src/user.dart index cff8a1201..ae8af4cdb 100644 --- a/lib/src/user.dart +++ b/lib/src/user.dart @@ -107,7 +107,7 @@ class User { /// ``` Future linkCredentials(Credentials credentials) async { final userHandle = await realmCore.userLinkCredentials(app, this, credentials); - return UserInternal.create(userHandle, app: app); + return UserInternal.create(userHandle, app); } @override @@ -148,25 +148,25 @@ class UserProfile { /// Gets the name of the [User]. String? get name => _data["name"] as String?; - + /// Gets the first name of the [User]. String? get firstName => _data["firstName"] as String?; /// Gets the last name of the [User]. String? get lastName => _data["lastName"] as String?; - + /// Gets the email of the [User]. String? get email => _data["email"] as String?; - + /// Gets the gender of the [User]. String? get gender => _data["gender"] as String?; - + /// Gets the birthday of the user. String? get birthDay => _data["birthDay"] as String?; - + /// Gets the minimum age of the [User]. String? get minAge => _data["minAge"] as String?; - + /// Gets the maximum age of the [User]. String? get maxAge => _data["maxAge"] as String?; @@ -174,7 +174,7 @@ class UserProfile { String? get pictureUrl => _data["pictureUrl"] as String?; /// Gets a profile property of the [User]. - dynamic operator[](String property) => _data[property]; + dynamic operator [](String property) => _data[property]; const UserProfile(this._data); } @@ -188,7 +188,7 @@ extension UserIdentityInternal on UserIdentity { extension UserInternal on User { UserHandle get handle => _handle; - static User create(UserHandle handle, {App? app}) { + static User create(UserHandle handle, [App? app]) { app ??= AppInternal.create(realmCore.userGetApp(handle)); return User._(app, handle); diff --git a/test/session_test.dart b/test/session_test.dart index 3a3af54e3..8b54666a5 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -38,7 +38,7 @@ Future main([List? args]) async { final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); - expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession.realmPath, realm.config.path); expect(realm.syncSession, realm.syncSession); }); @@ -46,7 +46,7 @@ Future main([List? args]) async { final realm = await getIntegrationRealm(); expect(realm.syncSession, isNotNull); - expect(realm.syncSession.path, realm.config.path); + expect(realm.syncSession.realmPath, realm.config.path); expect(realm.syncSession, realm.syncSession); }); @@ -71,56 +71,63 @@ Future main([List? args]) async { expect(realm.syncSession, isNotNull); }, skip: 'crashes'); - Future _validateSessionStates(Session session, {SessionState? sessionState, ConnectionState? connectionState}) async { - if (sessionState != null) { - expect(session.state.name, sessionState.name); + Future validateSessionStates(Session session, {SessionState? expectedSessionState, ConnectionState? expectedConnectionState}) async { + if (expectedSessionState != null) { + expect(session.state.name, expectedSessionState.name); } - if (connectionState != null) { - // The connection requires a bit of time to update its state - await Future.delayed(Duration(milliseconds: 100)); - expect(session.connectionState.name, connectionState.name); + if (expectedConnectionState != null) { + for (var i = 0; i < 5; i++) { + if (session.connectionState.name == expectedConnectionState.name) { + break; + } + + // The connection requires a bit of time to update its state + await Future.delayed(Duration(milliseconds: 100)); + } + + expect(session.connectionState.name, expectedConnectionState.name); } } baasTest('SyncSession.pause/resume', (configuration) async { final realm = await getIntegrationRealm(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active, expectedConnectionState: ConnectionState.connected); realm.syncSession.pause(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive, connectionState: ConnectionState.disconnected); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.inactive, expectedConnectionState: ConnectionState.disconnected); realm.syncSession.resume(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active, connectionState: ConnectionState.connected); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active, expectedConnectionState: ConnectionState.connected); }); baasTest('SyncSession.pause called multiple times is a no-op', (configuration) async { final realm = await getIntegrationRealm(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active); realm.syncSession.pause(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.inactive); // This should not do anything realm.syncSession.pause(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.inactive); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.inactive); }); baasTest('SyncSession.resume called multiple times is a no-op', (configuration) async { final realm = await getIntegrationRealm(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active); realm.syncSession.resume(); realm.syncSession.resume(); - await _validateSessionStates(realm.syncSession, sessionState: SessionState.active); + await validateSessionStates(realm.syncSession, expectedSessionState: SessionState.active); }); baasTest('SyncSession.waitForUpload with no changes', (configuration) async { @@ -297,3 +304,4 @@ class StreamProgressData { doneInvoked: other.doneInvoked, transferredBytes: other.transferredBytes); } +- \ No newline at end of file From e0939c749be7bd7cb5116dc97a89ebcd256c3f68 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 13:54:09 +0200 Subject: [PATCH 069/122] Fix tests --- test/app_test.dart | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/test/app_test.dart b/test/app_test.dart index 5c6803b59..f41c61c4f 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -60,17 +60,19 @@ Future main([List? args]) async { expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1)); expect(appConfig.httpClient, httpClient); }); - -test('AppConfiguration can be created with defaults', () { + + test('AppConfiguration can be created with defaults', () { final appConfig = AppConfiguration('myapp1'); - final app = App(appConfig); - expect(app.configuration.appId, 'myapp1'); - expect(app.configuration.baseUrl, Uri.parse('https://realm.mongodb.com')); - expect(app.configuration.defaultRequestTimeout, const Duration(minutes: 1)); - expect(app.configuration.logLevel, LogLevel.error); - expect(app.configuration.metadataPersistenceMode, MetadataPersistenceMode.plaintext); - expect(app.configuration.maxConnectionTimeout, const Duration(minutes: 2)); - expect(app.configuration.httpClient, isNotNull); + expect(appConfig.appId, 'myapp1'); + expect(appConfig.baseUrl, Uri.parse('https://realm.mongodb.com')); + expect(appConfig.defaultRequestTimeout, const Duration(minutes: 1)); + expect(appConfig.logLevel, LogLevel.error); + expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.plaintext); + expect(appConfig.maxConnectionTimeout, const Duration(minutes: 2)); + expect(appConfig.httpClient, isNotNull); + + // Check that the app constructor works + App(appConfig); }); test('AppConfiguration can be created', () { @@ -88,15 +90,18 @@ test('AppConfiguration can be created with defaults', () { maxConnectionTimeout: const Duration(minutes: 1), httpClient: httpClient, ); - final app = App(appConfig); - expect(app.configuration.appId, 'myapp1'); - expect(app.configuration.baseFilePath.path, Directory.systemTemp.path); - expect(app.configuration.baseUrl, Uri.parse('https://not_re.al')); - expect(app.configuration.defaultRequestTimeout, const Duration(seconds: 2)); - expect(app.configuration.logLevel, LogLevel.info); - expect(app.configuration.metadataPersistenceMode, MetadataPersistenceMode.encrypted); - expect(app.configuration.maxConnectionTimeout, const Duration(minutes: 1)); - expect(app.configuration.httpClient, httpClient); + + expect(appConfig.appId, 'myapp1'); + expect(appConfig.baseFilePath.path, Directory.systemTemp.path); + expect(appConfig.baseUrl, Uri.parse('https://not_re.al')); + expect(appConfig.defaultRequestTimeout, const Duration(seconds: 2)); + expect(appConfig.logLevel, LogLevel.info); + expect(appConfig.metadataPersistenceMode, MetadataPersistenceMode.encrypted); + expect(appConfig.maxConnectionTimeout, const Duration(minutes: 1)); + expect(appConfig.httpClient, httpClient); + + // Check that the app constructor works + App(appConfig); }); test('App can be created', () async { From 5de3e5858c25bfc6911ca747b0fd8084379162ca Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 14:01:37 +0200 Subject: [PATCH 070/122] Store the http transport as persistent handle --- lib/src/native/realm_core.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c4f34da78..f3de1d358 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -876,8 +876,6 @@ class _RealmCore { _realmLib.realm_app_config_set_platform(handle._pointer, Platform.operatingSystem.toUtf8Ptr(arena)); _realmLib.realm_app_config_set_platform_version(handle._pointer, Platform.operatingSystemVersion.toUtf8Ptr(arena)); - //This sets the realm lib version instead of the SDK version. - //TODO: Read the SDK version from code generated version field _realmLib.realm_app_config_set_sdk_version(handle._pointer, libraryVersion.toUtf8Ptr(arena)); return handle; @@ -899,8 +897,8 @@ class _RealmCore { RealmHttpTransportHandle _createHttpTransport(HttpClient httpClient) { return RealmHttpTransportHandle._(_realmLib.realm_http_transport_new( Pointer.fromFunction(request_callback), - httpClient.toWeakHandle(), - nullptr, + httpClient.toPersistentHandle(), + _deletePersistentHandleFuncPtr, )); } From 1db6963b626c28c7b5f749579ff53f1f5b43df80 Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 15:06:44 +0200 Subject: [PATCH 071/122] Remove - --- test/session_test.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/test/session_test.dart b/test/session_test.dart index 8b54666a5..802cdcce1 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -304,4 +304,3 @@ class StreamProgressData { doneInvoked: other.doneInvoked, transferredBytes: other.transferredBytes); } -- \ No newline at end of file From fbc3505532c9b595033b151550e7247051acea5d Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 15:22:24 +0200 Subject: [PATCH 072/122] Pick up config.sync rename --- test/session_test.dart | 4 ++-- test/test.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/session_test.dart b/test/session_test.dart index 802cdcce1..6d84f57ee 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -53,7 +53,7 @@ Future main([List? args]) async { baasTest('SyncSession.user returns a valid user', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.sync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, [Task.schema]); final realm = getRealm(config); expect(realm.syncSession.user, user); @@ -63,7 +63,7 @@ Future main([List? args]) async { baasTest('SyncSession when isolate is torn down does not crash', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.sync(user, [Task.schema]); + final config = Configuration.flexibleSync(user, [Task.schema]); // Don't use getRealm because we want the Realm to survive final realm = Realm(config); diff --git a/test/test.dart b/test/test.dart index 087ee419a..68098d725 100644 --- a/test/test.dart +++ b/test/test.dart @@ -302,7 +302,7 @@ Future getIntegrationRealm({App? app, ObjectId? differentiator, String? p final user = await getIntegrationUser(app); // TODO: path will not be needed after https://github.com/realm/realm-dart/pull/574 - final config = Configuration.sync(user, [Task.schema, Schedule.schema, NullableTypes.schema], path: path); + final config = Configuration.flexibleSync(user, [Task.schema, Schedule.schema, NullableTypes.schema], path: path); final realm = getRealm(config); if (differentiator != null) { realm.subscriptions.update((mutableSubscriptions) { From 95078c27549689fbd7a95bbab00441c6101bb9d9 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 16:45:29 +0300 Subject: [PATCH 073/122] Fixes after merge --- lib/src/list.dart | 3 +-- lib/src/results.dart | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/src/list.dart b/lib/src/list.dart index 698666322..7d3f70eb7 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -131,8 +131,7 @@ extension RealmListOfObject on RealmList { /// 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 arguments = const []]) { final managedList = asManaged(); - final queryHandle = realmCore.queryList(managedList, query, arguments); - final handle = realmCore.queryFindAll(queryHandle); + final handle = realmCore.queryList(managedList, query, arguments); return RealmResultsInternal.create(handle, realm); } diff --git a/lib/src/results.dart b/lib/src/results.dart index cf80d6c94..d11b93e92 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -48,8 +48,7 @@ class RealmResults extends collection.IterableBase { /// The Realm Dart and Realm Flutter SDKs supports querying based on a language inspired by [NSPredicate](https://academy.realm.io/posts/nspredicate-cheatsheet/) /// 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 queryHandle = realmCore.queryResults(this, query, args); - final handle = realmCore.queryFindAll(queryHandle); + final handle = realmCore.queryResults(this, query, args); return RealmResultsInternal.create(handle, realm); } From 66f582a3a85b711a4ab4ff8dec7fc2eda663dafe Mon Sep 17 00:00:00 2001 From: Nikola Irinchev Date: Wed, 18 May 2022 16:08:33 +0200 Subject: [PATCH 074/122] regenerate bindings --- lib/src/native/realm_bindings.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 163e1aa89..6563141f6 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -8845,7 +8845,7 @@ class RealmLibrary { /// Register a callback that will be invoked every time the session's connection state changes. /// - /// @return A token value that can be used to unregiser the callback. + /// @return A token value that can be used to unregister the callback. int realm_dart_sync_session_register_connection_state_change_callback( ffi.Pointer session, realm_sync_connection_state_changed_func_t callback, From b6065b395ba89ec508d034b439203a2e4201312d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 17:21:28 +0300 Subject: [PATCH 075/122] Fix API doc --- lib/src/configuration.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index e46428f21..3d5f675b8 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -46,7 +46,7 @@ typedef InitialDataCallback = void Function(Realm realm); ///The signature of a callback that will be invoked whenever a [SessionError] occurs for the synchronized Realm. /// -/// Client reset errors will not be reported through this callback as they are handled by the set [ClientResetHandler]. +/// Client reset errors will not be reported through this callback as they are handled by [ClientResetHandler]. typedef SessionErrorHandler = void Function(SessionError error); /// Configuration used to create a [Realm] instance @@ -222,6 +222,7 @@ class FlexibleSyncConfiguration extends Configuration { SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; + /// Called when a [SessionError] occurs for the synchronized Realm. final SessionErrorHandler? sessionErrorHandler; FlexibleSyncConfiguration._( From c7b9cc53dc9dd9ba102b719a8de685d5ceedd04d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 17:40:35 +0300 Subject: [PATCH 076/122] utii.dart deleted --- lib/src/util.dart | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 lib/src/util.dart diff --git a/lib/src/util.dart b/lib/src/util.dart deleted file mode 100644 index 34169a42c..000000000 --- a/lib/src/util.dart +++ /dev/null @@ -1,25 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// -// Copyright 2022 Realm Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -//////////////////////////////////////////////////////////////////////////////// - -extension NullableExtension on T? { - U? convert(U Function(T) converter) { - final self = this; - if (self == null) return null; - return converter(self); - } -} \ No newline at end of file From 639597822550bf3e71f27863dbcaae93f7406a90 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 17:50:48 +0300 Subject: [PATCH 077/122] Fix merges --- lib/src/native/realm_core.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b9b17e1f9..2ca0697af 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -190,7 +190,9 @@ class _RealmCore { 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_config_set_sync_config(configPtr, syncConfigPtr); + _realmLib.realm_sync_config_set_error_handler( + syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), nullptr); + _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { _realmLib.realm_release(syncConfigPtr.cast()); } From f63d0159c5d14fc0795c7bb38011af6f18bbda8c Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 18 May 2022 21:23:44 +0300 Subject: [PATCH 078/122] Remove -DREALM_ENABLE_SYNC=1 --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f7fd3212b..47ecb1bf9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -23,7 +23,7 @@ set(HEADERS ) add_library(realm_dart SHARED ${SOURCES} ${HEADERS}) -target_compile_definitions(RealmFFIStatic PUBLIC -DRealm_EXPORTS -DREALM_ENABLE_SYNC=1) +target_compile_definitions(RealmFFIStatic PUBLIC -DRealm_EXPORTS) target_link_libraries(realm_dart dart-dl RealmFFIStatic Realm::Storage) From b0d89dd7891eb61b8cfdde575395f9360e0d3caf Mon Sep 17 00:00:00 2001 From: blagoev Date: Thu, 19 May 2022 08:59:34 +0300 Subject: [PATCH 079/122] small fixes to another PR --- lib/src/subscription.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 1112e232b..d3e2291c8 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -31,7 +31,7 @@ class Subscription { Subscription._(this._handle); - ObjectId get _id => realmCore.subscriptionId(this); + late final ObjectId _id = realmCore.subscriptionId(this); /// Name of the [Subscription], if one was provided at creation time. String? get name => realmCore.subscriptionName(this); @@ -206,11 +206,11 @@ extension SubscriptionSetInternal on SubscriptionSet { Realm get realm => _realm; SubscriptionSetHandle get handle => _handle; - static SubscriptionSet create(Realm realm, SubscriptionSetHandle handle) => _ImmutableSubscriptionSet._(realm, handle); + static SubscriptionSet create(Realm realm, SubscriptionSetHandle handle) => ImmutableSubscriptionSet._(realm, handle); } -class _ImmutableSubscriptionSet extends SubscriptionSet { - _ImmutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle) : super._(realm, handle); +class ImmutableSubscriptionSet extends SubscriptionSet { + ImmutableSubscriptionSet._(Realm realm, SubscriptionSetHandle handle) : super._(realm, handle); @override void update(void Function(MutableSubscriptionSet mutableSubscriptions) action) { From 4e02089a17ced329c51ef553a111c5c0f11ffe9c Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 19 May 2022 16:34:36 +0300 Subject: [PATCH 080/122] Merge branch 'master' into sync_config_error_handler --- common/lib/src/realm_types.dart | 4 ++-- lib/src/native/realm_core.dart | 11 +++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index abea694be..0fb63eec0 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -87,8 +87,8 @@ class SessionError extends SyncError { SessionError( String message, - this.isFatal, { - SyncErrorCategory category = SyncErrorCategory.unknown, + SyncErrorCategory category, { + this.isFatal = false, int code = 0, }) : super(message, category, code); } diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 6ff7e5925..d0bf1840f 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -187,8 +187,9 @@ class _RealmCore { try { _realmLib.realm_sync_config_set_session_stop_policy(syncConfigPtr, config.sessionStopPolicy.index); _realmLib.realm_sync_config_set_error_handler( - syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), nullptr); - _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); + syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), + _realmLib.addresses.realm_dart_delete_persistent_handle); + _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { _realmLib.realm_release(syncConfigPtr.cast()); } @@ -396,10 +397,8 @@ class _RealmCore { if (syncConfig == null) { return; } - if (syncConfig.sessionErrorHandler != null) { - final sessionError = error.toSessionError(); - syncConfig.sessionErrorHandler!(sessionError); - } + final sessionError = error.toSessionError(); + syncConfig.sessionErrorHandler!(sessionError); } SchedulerHandle createScheduler(int isolateId, int sendPort) { From 4f45e36fbce3ed76cce4a4405d06484b90e8c497 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 23 May 2022 10:25:08 +0300 Subject: [PATCH 081/122] Merge branch 'master' into sync_config_error_handler --- lib/src/native/realm_core.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 16bb10366..7d5cc51f0 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -186,8 +186,7 @@ class _RealmCore { 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_error_handler( - syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), + _realmLib.realm_sync_config_set_error_handler(syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle); _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { @@ -1863,8 +1862,8 @@ extension on realm_sync_error { return SessionError( messageText, - isFatal, - category: errorCategory, + errorCategory, + isFatal: isFatal, code: error_code.value, ); } From e569ea64ae524d822710d218946a7c9b1046e8e1 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 23 May 2022 11:26:27 +0300 Subject: [PATCH 082/122] Test error handler with simulating error --- lib/src/native/realm_core.dart | 4 ++++ lib/src/session.dart | 4 ++++ src/sync_session.cpp | 18 +++++++++++++++++- src/sync_session.h | 12 +++++++++++- test/session_test.dart | 13 +++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 7d5cc51f0..b8f29d5cf 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -400,6 +400,10 @@ class _RealmCore { syncConfig.sessionErrorHandler!(sessionError); } + void raiseError(Session session, SyncErrorCategory category, int errorCode, bool isFatal) { + _realmLib.realm_dart_sync_session_report_error_for_testing(session.handle._pointer, category.index, errorCode, isFatal); + } + SchedulerHandle createScheduler(int isolateId, int sendPort) { final schedulerPtr = _realmLib.realm_dart_create_scheduler(isolateId, sendPort); return SchedulerHandle._(schedulerPtr); diff --git a/lib/src/session.dart b/lib/src/session.dart index 08bf8d654..319b0a750 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -71,6 +71,10 @@ class Session { final controller = SessionProgressNotificationsController(this, direction, mode); return controller.createStream(); } + + void _raiseSessionError(SyncErrorCategory category, int errorCode, bool isFatal) { + realmCore.raiseError(this, category, errorCode, isFatal); + } } /// The current state of a [Session] object diff --git a/src/sync_session.cpp b/src/sync_session.cpp index 4cd0ea4ac..42b99cdfc 100644 --- a/src/sync_session.cpp +++ b/src/sync_session.cpp @@ -17,10 +17,13 @@ //////////////////////////////////////////////////////////////////////////////// #include - +#include +#include +#include #include "sync_session.h" #include "event_loop_dispatcher.hpp" + namespace realm::c_api { namespace _1 { @@ -125,4 +128,17 @@ RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callba } // anonymous namespace +RLM_API void realm_dart_sync_session_report_error_for_testing(realm_sync_session_t* session, uint32_t category, int errorCode, bool isFatal) noexcept +{ + std::error_code error_code; + if (category == 0) { + error_code = std::error_code(errorCode, realm::sync::client_error_category()); + } + else + { + error_code = std::error_code(errorCode, realm::sync::protocol_error_category()); + + } + SyncSession::OnlyForTesting::handle_error(*(*session), SyncError{ error_code,"Error", isFatal }); +} } // namespace realm::c_api diff --git a/src/sync_session.h b/src/sync_session.h index 62bcbcdce..80da6b024 100644 --- a/src/sync_session.h +++ b/src/sync_session.h @@ -68,5 +68,15 @@ RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callba realm_free_userdata_func_t userdata_free, realm_scheduler_t* scheduler) RLM_API_NOEXCEPT; - +/** + * Simulates a session error. + * + * @param session The session where the simulated error will occur. + * @param category The category of the error that to be simulated (client=0, connection=1, session=2, system=3, unknown=4) + * @param errorCode Error code of the error that to be simulated. + * @param isFatal >If set to `true` the error will be marked as fatal. + * + * Use this method to test your error handling code without connecting to a MongoDB Realm Server. + */ +RLM_API void realm_dart_sync_session_report_error_for_testing(realm_sync_session_t* session, uint32_t category, int errorCode, bool isFatal) RLM_API_NOEXCEPT; #endif // REALM_DART_SYNC_SESSION_H \ No newline at end of file diff --git a/test/session_test.dart b/test/session_test.dart index 57fc7a9f6..d41d7f87b 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -281,6 +281,19 @@ Future main([List? args]) async { await uploadData.subscription.cancel(); await downloadData.subscription.cancel(); }); + + baasTest('SyncSession.user returns a valid user', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { + expect(sessionError.category, SyncErrorCategory.session); + expect(sessionError.isFatal, false); + expect(sessionError.code, 100); + expect(sessionError.message, "Error"); + }); + final realm = getRealm(config); + realm.syncSession._raiseSessionError(SyncErrorCategory.session, 100, false); + }); } class StreamProgressData { From 4b7cb58e8e243ac26b1b6e366cb45cd03aa5eec0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 23 May 2022 12:26:29 +0300 Subject: [PATCH 083/122] Merge PR #587 into this one --- lib/src/native/realm_bindings.dart | 39 ++++++++++++++++++++++++++++++ lib/src/session.dart | 6 +++++ test/session_test.dart | 6 +++-- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 3eedef146..9c45ae605 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -3271,6 +3271,39 @@ class RealmLibrary { realm_free_userdata_func_t, ffi.Pointer)>(); + /// Simulates a session error. + /// + /// @param session The session where the simulated error will occur. + /// @param category The category of the error that to be simulated (client=0, connection=1, session=2, system=3, unknown=4) + /// @param errorCode Error code of the error that to be simulated. + /// @param isFatal >If set to `true` the error will be marked as fatal. + /// + /// Use this method to test your error handling code without connecting to a MongoDB Realm Server. + void realm_dart_sync_session_report_error_for_testing( + ffi.Pointer session, + int category, + int errorCode, + bool isFatal, + ) { + return _realm_dart_sync_session_report_error_for_testing( + session, + category, + errorCode, + isFatal ? 1 : 0, + ); + } + + late final _realm_dart_sync_session_report_error_for_testingPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Uint32, + ffi.Int32, + ffi.Uint8)>>('realm_dart_sync_session_report_error_for_testing'); + late final _realm_dart_sync_session_report_error_for_testing = + _realm_dart_sync_session_report_error_for_testingPtr.asFunction< + void Function(ffi.Pointer, int, int, int)>(); + /// Register a callback that will be invoked when all pending downloads have completed. void realm_dart_sync_session_wait_for_download_completion( ffi.Pointer session, @@ -9131,6 +9164,12 @@ class _SymbolAddresses { ffi.Pointer)>> get realm_dart_sync_session_register_progress_notifier => _library._realm_dart_sync_session_register_progress_notifierPtr; + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function(ffi.Pointer, ffi.Uint32, + ffi.Int32, ffi.Uint8)>> + get realm_dart_sync_session_report_error_for_testing => + _library._realm_dart_sync_session_report_error_for_testingPtr; ffi.Pointer< ffi.NativeFunction< ffi.Void Function( diff --git a/lib/src/session.dart b/lib/src/session.dart index 319b0a750..4119c3480 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -143,6 +143,12 @@ extension SessionInternal on Session { Scheduler get scheduler => _scheduler; } +extension SessionDevInternal on Session { + void raiseSessionError(SyncErrorCategory category, int errorCode, bool isFatal) { + _raiseSessionError(category, errorCode, isFatal); + } +} + /// @nodoc class SessionProgressNotificationsController { final Session _session; diff --git a/test/session_test.dart b/test/session_test.dart index d41d7f87b..30bc9c16f 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -21,6 +21,7 @@ import 'dart:io'; import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; +import '../lib/src/session.dart' show SessionDevInternal; import 'test.dart'; Future main([List? args]) async { @@ -282,7 +283,7 @@ Future main([List? args]) async { await downloadData.subscription.cancel(); }); - baasTest('SyncSession.user returns a valid user', (configuration) async { + baasTest('SyncSession test error handles', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { @@ -292,7 +293,8 @@ Future main([List? args]) async { expect(sessionError.message, "Error"); }); final realm = getRealm(config); - realm.syncSession._raiseSessionError(SyncErrorCategory.session, 100, false); + + realm.syncSession.raiseSessionError(SyncErrorCategory.session, 100, false); }); } From 0f9d440f6fda7266ddddf334795c26b73fce347c Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 23 May 2022 12:48:42 +0300 Subject: [PATCH 084/122] Add session tests to Flutter tests --- flutter/realm_flutter/tests/test_driver/realm_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/flutter/realm_flutter/tests/test_driver/realm_test.dart b/flutter/realm_flutter/tests/test_driver/realm_test.dart index 5284f9f84..951f72d73 100644 --- a/flutter/realm_flutter/tests/test_driver/realm_test.dart +++ b/flutter/realm_flutter/tests/test_driver/realm_test.dart @@ -15,6 +15,7 @@ import '../test/credentials_test.dart' as credentials_tests; import '../test/app_test.dart' as app_tests; import '../test/user_test.dart' as user_tests; import '../test/subscription_test.dart' as subscription_test; +import '../test/session_test.dart' as session_test; Future main(List args) async { final Completer completer = Completer(); @@ -29,7 +30,8 @@ Future main(List args) async { await app_tests.main(args); await user_tests.main(args); await subscription_test.main(args); - + await session_test.main(args); + tearDown(() { if (Invoker.current?.liveTest.state.result == test_api.Result.error || Invoker.current?.liveTest.state.result == test_api.Result.failure) { failedTests.add(Invoker.current!.liveTest.individualName); From 1fff5f3e6d4d821127b652c8bd2fbb3643f4d09f Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 23 May 2022 15:41:18 +0300 Subject: [PATCH 085/122] Fix the text of simulated errors messages --- src/sync_session.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sync_session.cpp b/src/sync_session.cpp index 42b99cdfc..0f23c46e0 100644 --- a/src/sync_session.cpp +++ b/src/sync_session.cpp @@ -131,14 +131,17 @@ RLM_API uint64_t realm_dart_sync_session_register_connection_state_change_callba RLM_API void realm_dart_sync_session_report_error_for_testing(realm_sync_session_t* session, uint32_t category, int errorCode, bool isFatal) noexcept { std::error_code error_code; + std::string msg; if (category == 0) { error_code = std::error_code(errorCode, realm::sync::client_error_category()); + msg = "Simulated client reset error"; } else { error_code = std::error_code(errorCode, realm::sync::protocol_error_category()); + msg = "Simulated sync session error"; } - SyncSession::OnlyForTesting::handle_error(*(*session), SyncError{ error_code,"Error", isFatal }); + SyncSession::OnlyForTesting::handle_error(*(*session), SyncError{ error_code, msg, isFatal }); } } // namespace realm::c_api From 89679d745b3f9e0b15aae0fa9ec368c232d2ee3d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 23 May 2022 23:37:59 +0300 Subject: [PATCH 086/122] Implement clientResetHandler --- common/lib/src/realm_types.dart | 14 ++++++++++++-- lib/src/configuration.dart | 15 +++++++++++++-- lib/src/native/realm_core.dart | 7 ++++++- lib/src/realm_class.dart | 1 + src/sync_session.cpp | 19 +++++++++++-------- test/session_test.dart | 32 ++++++++++++++++++++++++++++++-- 6 files changed, 73 insertions(+), 15 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 0fb63eec0..74af76af8 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -79,8 +79,18 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } +/// An error type that describes a client reset error condition. +/// {@category Sync} +class ClientResetError extends SessionError { + ClientResetError( + String message, + SyncErrorCategory category, { + bool isFatal = false, + }) : super(message, category, isFatal: isFatal, code: 132); //Code 132: ClientError.auto_client_reset_failure +} + /// An error type that describes a session-level error condition. -/// /// {@category Sync} +/// {@category Sync} class SessionError extends SyncError { /// If true the received error is fatal. final bool isFatal; @@ -97,7 +107,7 @@ class SessionError extends SyncError { /// {@category Sync} class SyncError extends RealmError { /// The code of the error - final int code; // TODO: this should be an enum + final int code; // TODO: this should be an enum. There are two error codes enums in C-API realm::sync::ProtocolError and realm::sync::ClientError /// The category of the error final SyncErrorCategory category; diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index fc2494765..7f5336915 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -49,6 +49,11 @@ typedef InitialDataCallback = void Function(Realm realm); /// Client reset errors will not be reported through this callback as they are handled by [ClientResetHandler]. typedef SessionErrorHandler = void Function(SessionError error); +/// The signature of a callback that will be invoked if a client reset error occurs for this [Realm]. +/// +/// Currently, Flexible sync only supports the Manual Recovery. +typedef ClientResetHandler = void Function(SessionError error); + /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration { @@ -142,6 +147,7 @@ abstract class Configuration { String? fifoFilesFallbackPath, String? path, SessionErrorHandler? sessionErrorHandler, + ClientResetHandler? clientResetHandler, }) => FlexibleSyncConfiguration._( user, @@ -149,6 +155,7 @@ abstract class Configuration { fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, sessionErrorHandler: sessionErrorHandler, + clientResetHandler: clientResetHandler, ); } @@ -222,16 +229,20 @@ class FlexibleSyncConfiguration extends Configuration { final User user; SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; - - /// Called when a [SessionError] occurs for the synchronized Realm. + + /// Called when a [SessionError] occurs for the synchronized [Realm]. final SessionErrorHandler? sessionErrorHandler; + /// Called when a [ClientResetError] occurs for this [Realm] + final ClientResetHandler? clientResetHandler; + FlexibleSyncConfiguration._( this.user, List schemaObjects, { String? fifoFilesFallbackPath, String? path, this.sessionErrorHandler, + this.clientResetHandler, }) : super._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c9a18870b..3520429c8 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -407,7 +407,12 @@ class _RealmCore { return; } final sessionError = error.toSessionError(); - syncConfig.sessionErrorHandler!(sessionError); + if (error.is_client_reset_requested == 0 && syncConfig.sessionErrorHandler != null) { + syncConfig.sessionErrorHandler!(sessionError); + } + if (error.is_client_reset_requested == 1 && syncConfig.clientResetHandler != null) { + syncConfig.clientResetHandler!(ClientResetError(sessionError.message!, sessionError.category, isFatal: sessionError.isFatal)); + } } void raiseError(Session session, SyncErrorCategory category, int errorCode, bool isFatal) { diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index c3367ce9d..22c74606e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -38,6 +38,7 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, + ClientResetError, SessionError, SyncError, SyncErrorCategory, diff --git a/src/sync_session.cpp b/src/sync_session.cpp index 0f23c46e0..4370af78e 100644 --- a/src/sync_session.cpp +++ b/src/sync_session.cpp @@ -132,16 +132,19 @@ RLM_API void realm_dart_sync_session_report_error_for_testing(realm_sync_session { std::error_code error_code; std::string msg; - if (category == 0) { + std::uint8_t throwError = 0; + if (category == realm_sync_error_category::RLM_SYNC_ERROR_CATEGORY_CLIENT) { error_code = std::error_code(errorCode, realm::sync::client_error_category()); - msg = "Simulated client reset error"; + msg = "Simulated client error"; + throwError = 1; } - else - { + else if (category == realm_sync_error_category::RLM_SYNC_ERROR_CATEGORY_SESSION) { error_code = std::error_code(errorCode, realm::sync::protocol_error_category()); - msg = "Simulated sync session error"; - + msg = "Simulated session error"; + throwError = 1; + } + if (throwError == 1) { + SyncSession::OnlyForTesting::handle_error(*(*session), SyncError{ error_code, msg, isFatal }); } - SyncSession::OnlyForTesting::handle_error(*(*session), SyncError{ error_code, msg, isFatal }); } -} // namespace realm::c_api +} // namespace realm::c_api diff --git a/test/session_test.dart b/test/session_test.dart index 30bc9c16f..b274e2353 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -283,19 +283,47 @@ Future main([List? args]) async { await downloadData.subscription.cancel(); }); - baasTest('SyncSession test error handles', (configuration) async { + baasTest('SyncSession test error handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { expect(sessionError.category, SyncErrorCategory.session); expect(sessionError.isFatal, false); expect(sessionError.code, 100); - expect(sessionError.message, "Error"); + expect(sessionError.message, "Simulated session error"); }); final realm = getRealm(config); realm.syncSession.raiseSessionError(SyncErrorCategory.session, 100, false); }); + + baasTest('SyncSession test fatal error handler', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { + expect(sessionError.category, SyncErrorCategory.client); + expect(sessionError.isFatal, true); + expect(sessionError.code, 111); + expect(sessionError.message, "Simulated client error"); + }); + final realm = getRealm(config); + + realm.syncSession.raiseSessionError(SyncErrorCategory.client, 111, true); + }); + + baasTest('SyncSession client reset handler', (configuration) async { + final app = App(configuration); + final user = await getIntegrationUser(app); + final config = Configuration.flexibleSync(user, [Task.schema], clientResetHandler: (clientResetError) { + expect(clientResetError.category, SyncErrorCategory.session); + expect(clientResetError.isFatal, true); + expect(clientResetError.code, 132); // 132: ClientError.auto_client_reset_failure + expect(clientResetError.message, "Simulated session error"); + }); + final realm = getRealm(config); + + realm.syncSession.raiseSessionError(SyncErrorCategory.session, 132, true); + }); } class StreamProgressData { From 97c1ea9f8dbcb472a09dea18cf23f9338da6f8f0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Mon, 23 May 2022 23:42:31 +0300 Subject: [PATCH 087/122] Update test/session_test.dart --- test/session_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/session_test.dart b/test/session_test.dart index 30bc9c16f..9d9d01ddc 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -283,7 +283,7 @@ Future main([List? args]) async { await downloadData.subscription.cancel(); }); - baasTest('SyncSession test error handles', (configuration) async { + baasTest('SyncSession test error handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { From 6c252a36f8809f872898fc1c93e63d3e172d58c3 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Mon, 23 May 2022 23:43:12 +0300 Subject: [PATCH 088/122] Update test/session_test.dart --- test/session_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/session_test.dart b/test/session_test.dart index 9d9d01ddc..a366bfe01 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -290,7 +290,7 @@ Future main([List? args]) async { expect(sessionError.category, SyncErrorCategory.session); expect(sessionError.isFatal, false); expect(sessionError.code, 100); - expect(sessionError.message, "Error"); + expect(sessionError.message, "Simulated sync session error"); }); final realm = getRealm(config); From fe92f6a84252003b0e3dcaa7acc383e8a3070b5e Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Mon, 23 May 2022 23:48:52 +0300 Subject: [PATCH 089/122] Update lib/src/native/realm_core.dart --- lib/src/native/realm_core.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c9a18870b..e25f41edd 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -407,7 +407,9 @@ class _RealmCore { return; } final sessionError = error.toSessionError(); - syncConfig.sessionErrorHandler!(sessionError); + if(syncConfig.sessionErrorHandler != null) { + syncConfig.sessionErrorHandler!(sessionError); + } } void raiseError(Session session, SyncErrorCategory category, int errorCode, bool isFatal) { From 61d2a67add7bf691be2a6fec09018bc3ee02fd01 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 24 May 2022 00:20:55 +0300 Subject: [PATCH 090/122] print errors --- lib/src/native/realm_core.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index e25f41edd..c5bfed1b7 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -402,6 +402,7 @@ class _RealmCore { } static void _syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { + print(error.detailed_message.cast().toRealmDartString()!); final FlexibleSyncConfiguration? syncConfig = userdata.toObject(isPersistent: true); if (syncConfig == null) { return; From bcf5d340e9fb90a22089bf9593ab02cebe9462ef Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 25 May 2022 23:34:42 +0300 Subject: [PATCH 091/122] Code review changes --- CHANGELOG.md | 3 ++- src/sync_session.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a97c62727..85c4afa9f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,8 @@ * Support user profile data. ([#570](https://github.com/realm/realm-dart/pull/570)) * Support flexible synchronization. ([#496](https://github.com/realm/realm-dart/pull/496)) * Added support for DateTime properties. ([#569](https://github.com/realm/realm-dart/pull/569)) -* Support session error handler. ([#577](https://github.com/realm/realm-dart/pull/577)) +* Support SessionErrorHandler in FlexibleSyncConfiguration. ([#577](https://github.com/realm/realm-dart/pull/577)) +* Support ClientResetHandler in FlexibleSyncConfiguration. ([#608](https://github.com/realm/realm-dart/pull/608)) * Support setting logger on AppConfiguration. ([#583](https://github.com/realm/realm-dart/pull/583)) * Support setting logger on Realm class. Default is to print info message or worse to the console. ([#583](https://github.com/realm/realm-dart/pull/583)) diff --git a/src/sync_session.cpp b/src/sync_session.cpp index 4370af78e..6f41edfa5 100644 --- a/src/sync_session.cpp +++ b/src/sync_session.cpp @@ -132,18 +132,18 @@ RLM_API void realm_dart_sync_session_report_error_for_testing(realm_sync_session { std::error_code error_code; std::string msg; - std::uint8_t throwError = 0; + bool throwError = false; if (category == realm_sync_error_category::RLM_SYNC_ERROR_CATEGORY_CLIENT) { error_code = std::error_code(errorCode, realm::sync::client_error_category()); msg = "Simulated client error"; - throwError = 1; + throwError = true; } else if (category == realm_sync_error_category::RLM_SYNC_ERROR_CATEGORY_SESSION) { error_code = std::error_code(errorCode, realm::sync::protocol_error_category()); msg = "Simulated session error"; - throwError = 1; + throwError = true; } - if (throwError == 1) { + if (throwError) { SyncSession::OnlyForTesting::handle_error(*(*session), SyncError{ error_code, msg, isFatal }); } } From 0f82c2ec83b44fae65adc222943f8af4226744fd Mon Sep 17 00:00:00 2001 From: blagoev Date: Sat, 28 May 2022 10:53:02 +0300 Subject: [PATCH 092/122] generic sync error types --- common/lib/src/realm_types.dart | 364 ++++++++++++++++++++++++++++---- 1 file changed, 321 insertions(+), 43 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 74af76af8..dc6e1263e 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -79,40 +79,89 @@ class RealmStateError extends StateError implements RealmError { RealmStateError(String message) : super(message); } -/// An error type that describes a client reset error condition. +/// @nodoc +class Decimal128 {} // TODO! + +/// @nodoc +class RealmObjectMarker {} + +// Union type +/// @nodoc +class RealmAny { + final dynamic value; + T as() => value as T; // better for code completion + + // This is private, so user cannot accidentally construct an invalid instance + const RealmAny._(this.value); + + const RealmAny.bool(bool b) : this._(b); + const RealmAny.string(String text) : this._(text); + const RealmAny.int(int i) : this._(i); + const RealmAny.float(Float f) : this._(f); + const RealmAny.double(double d) : this._(d); + const RealmAny.uint8List(Uint8List data) : this._(data); + // TODO: RealmObjectMarker introduced to avoid dependency invertion. + // It would be better if we could use RealmObject directly + const RealmAny.realmObject(RealmObjectMarker o) : this._(o); + const RealmAny.dateTime(DateTime timestamp) : this._(timestamp); + const RealmAny.objectId(ObjectId id) : this._(id); + const RealmAny.decimal128(Decimal128 decimal) : this._(decimal); + const RealmAny.uuid(Uuid uuid) : this._(uuid); +} + +/// Thrown when an error occurs during synchronization /// {@category Sync} -class ClientResetError extends SessionError { - ClientResetError( - String message, - SyncErrorCategory category, { - bool isFatal = false, - }) : super(message, category, isFatal: isFatal, code: 132); //Code 132: ClientError.auto_client_reset_failure +class SyncError extends RealmError { + /// The code indicating the type of the sync error. + final T code; + + /// The category of the sync error + final SyncErrorCategory category; + + SyncError(String message, this.category, this.code) : super(message); + + static SyncError create(String message, SyncErrorCategory category, int code) { + switch (category) { + case SyncErrorCategory.client: + return SyncError(message, category, SyncClientErrorCode.values[code]); + case SyncErrorCategory.connection: + return SyncError(message, category, SyncConnectionErrorCode.values[code]); + case SyncErrorCategory.session: + return SyncError(message, category, SyncSessionErrorCode.values[code]); + case SyncErrorCategory.system: + case SyncErrorCategory.unknown: + default: + return SyncError(message, category, SyncGeneralErrorCode.generalError); + } + } +} + +/// A general or unknown sync error +class GeneralSyncError extends SyncError { + @override + final int generalError; + + GeneralSyncError(String message, SyncErrorCategory category, this.code) : super(message, category, SyncGeneralErrorCode.generalError); } /// An error type that describes a session-level error condition. /// {@category Sync} -class SessionError extends SyncError { +class SessionError extends SyncError { /// If true the received error is fatal. final bool isFatal; SessionError( String message, - SyncErrorCategory category, { + SyncErrorCategory category, + T code, { this.isFatal = false, - int code = 0, }) : super(message, category, code); } -/// Thrown or reporeted if an error occurs during synchronization +/// An error type that describes a client reset error condition. /// {@category Sync} -class SyncError extends RealmError { - /// The code of the error - final int code; // TODO: this should be an enum. There are two error codes enums in C-API realm::sync::ProtocolError and realm::sync::ClientError - - /// The category of the error - final SyncErrorCategory category; - - SyncError(String message, this.category, this.code) : super(message); +class ClientResetError extends SessionError { + ClientResetError(String message) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure, isFatal: true); } /// The category of a [SyncError]. @@ -133,32 +182,261 @@ enum SyncErrorCategory { unknown, } -/// @nodoc -class Decimal128 {} // TODO! +enum SyncGeneralErrorCode { + generalError(999) +} -/// @nodoc -class RealmObjectMarker {} +/// Protocol errors discovered by the client. +/// +/// These errors will terminate the network connection +/// (disconnect all sessions associated with the affected connection), +/// and the error will be reported via the connection state change listeners of the affected sessions. +enum SyncClientErrorCode { + /// Connection closed (no error) + connectionClosed(100), -// Union type -/// @nodoc -class RealmAny { - final dynamic value; - T as() => value as T; // better for code completion + /// Unknown type of input message + unknownMessage(101), - // This is private, so user cannot accidentally construct an invalid instance - const RealmAny._(this.value); + /// Bad syntax in input message head + badSyntax(102), - const RealmAny.bool(bool b) : this._(b); - const RealmAny.string(String text) : this._(text); - const RealmAny.int(int i) : this._(i); - const RealmAny.float(Float f) : this._(f); - const RealmAny.double(double d) : this._(d); - const RealmAny.uint8List(Uint8List data) : this._(data); - // TODO: RealmObjectMarker introduced to avoid dependency invertion. - // It would be better if we could use RealmObject directly - const RealmAny.realmObject(RealmObjectMarker o) : this._(o); - const RealmAny.dateTime(DateTime timestamp) : this._(timestamp); - const RealmAny.objectId(ObjectId id) : this._(id); - const RealmAny.decimal128(Decimal128 decimal) : this._(decimal); - const RealmAny.uuid(Uuid uuid) : this._(uuid); + /// Limits exceeded in input message + limitsExceeded(103), + + /// Bad session identifier in input message + badSessionIdent(104), + + /// Bad input message order + badMessageOrder(105), + + /// Bad client file identifier (IDENT) + badClientFileIdent(106), + + /// Bad progress information (DOWNLOAD) + badProgress(107), + + /// Bad syntax in changeset header (DOWNLOAD) + badChangesetHeaderSyntax(108), + + /// Bad changeset size in changeset header (DOWNLOAD) + badChangesetSize(109), + + /// Bad origin file identifier in changeset header (DOWNLOAD) + badOriginFileIdent(110), + + /// Bad server version in changeset header (DOWNLOAD) + badServerVersion(111), + + /// Bad changeset (DOWNLOAD) + badChangeset(112), + + /// Bad request identifier (MARK) + badRequestIdent(113), + + /// Bad error code (ERROR), + badErrorCode(114), + + /// Bad compression (DOWNLOAD) + badCompression(115), + + /// Bad last integrated client version in changeset header (DOWNLOAD) + badClientVersion(116), + + /// SSL server certificate rejected + sslServerCertRejected(117), + + /// Timeout on reception of PONG respone message + pongTimeout(118), + + /// Bad client file identifier salt (IDENT) + badClientFileIdentSalt(119), + + /// Bad file identifier (ALLOC) + badFileIdent(120), + + /// Sync connection was not fully established in time + connectTimeout(121), + + /// Bad timestamp (PONG) + badTimestamp(122), + + /// Bad or missing protocol version information from server + badProtocolFromServer(123), + + /// Protocol version negotiation failed: Client is too old for server + clientTooOldForServer(124), + + /// Protocol version negotiation failed: Client is too new for server + clientTooNewForServer(125), + + /// Protocol version negotiation failed: No version supported by both client and server + protocolMismatch(126), + + /// Bad values in state message (STATE) + badStateMessage(127), + + /// Requested feature missing in negotiated protocol version + missingProtocolFeature(128), + + /// Failed to establish HTTP tunnel with configured proxy + httpTunnelFailed(131), + + /// A fatal error was encountered which prevents completion of a client reset + autoClientResetFailure(132); + + final int code; + const SyncClientErrorCode(this.code); +} + +/// Protocol connection errors discovered by the server, and reported to the client +/// +/// These errors will be reported via the error handlers of the affected sessions. +enum SyncConnectionErrorCode { + // Connection level and protocol errors + /// Connection closed (no error) + connectionClosed(100), + + /// Other connection level error + otherError(101), + + /// Unknown type of input message + unknownMessage(102), + + /// Bad syntax in input message head + badSyntax(103), + + /// Limits exceeded in input message + limitsExceeded(104), + + /// Wrong protocol version (CLIENT) (obsolete) + wrongProtocolVersion(105), + + /// Bad session identifier in input message + badSessionIdent(106), + + /// Overlapping reuse of session identifier (BIND) + reuseOfSessionIdent(107), + + /// Client file bound in other session (IDENT) + boundInOtherSession(108), + + /// Bad input message order + badMessageOrder(109), + + /// Error in decompression (UPLOAD) + badDecompression(110), + + /// Bad syntax in a changeset header (UPLOAD) + badChangesetHeaderSyntax(111), + + /// Bad size specified in changeset header (UPLOAD) + badChangesetSize(112), + + /// Connected with wrong wire protocol - should switch to FLX sync + switchToFlxSync(113), + + /// Connected with wrong wire protocol - should switch to PBS + switchToPbs(114); + + final int code; + const SyncConnectionErrorCode(this.code); +} + +/// Protocol session errors discovered by the server, and reported to the client +/// +/// These errors will be reported via the error handlers of the affected sessions. +enum SyncSessionErrorCode { + /// Session closed (no error) + sessionClosed(200), + + /// Other session level error + otherSessionError(201), + + /// Access token expired + tokenExpired(202), + + /// Bad user authentication (BIND) + badAuthentication(203), + + /// Illegal Realm path (BIND) + illegalRealmPath(204), + + /// No such Realm (BIND) + noSuchRealm(205), + + /// Permission denied (BIND) + permissionDenied(206), + + /// Bad server file identifier (IDENT) (obsolete!) + badServerFileIdent(207), + + /// Bad client file identifier (IDENT) + badClientFileIdent(208), + + /// Bad server version (IDENT, UPLOAD, TRANSACT) + badServerVersion(209), + + /// Bad client version (IDENT, UPLOAD) + badClientVersion(210), + + /// Diverging histories (IDENT) + divergingHistories(211), + + /// Bad changeset (UPLOAD) + badChangeset(212), + + /// Partial sync disabled (BIND) + partialSyncDisabled(214), + + /// Unsupported session-level feature + unsupportedSessionFeature(215), + + /// Bad origin file identifier (UPLOAD) + badOriginFileIdent(216), + + /// Synchronization no longer possible for client-side file + badClientFile(217), + + /// Server file was deleted while session was bound to it + serverFileDeleted(218), + + /// Client file has been blacklisted (IDENT) + clientFileBlacklisted(219), + + /// User has been blacklisted (BIND) + userBlacklisted(220), + + /// Serialized transaction before upload completion + transactBeforeUpload(221), + + /// Client file has expired + clientFileExpired(222), + + /// User mismatch for client file identifier (IDENT) + userMismatch(223), + + /// Too many sessions in connection (BIND) + tooManySessions(224), + + /// Invalid schema change (UPLOAD) + invalidSchemaChange(225), + + /// Client query is invalid/malformed (IDENT, QUERY) + badQuery(226), + + /// Client tried to create an object that already exists outside their (()UPLOAD) + objectAlreadyExists(227), + + /// Server permissions for this file ident have changed since the last time it (used) (IDENT) + serverPermissionsChanged(228), + + /// Client tried to open a session before initial sync is complete (BIND) + initialSyncNotCompleted(229), + + /// Client attempted a write that is disallowed by permissions, or modifies an object outside the current query - requires client reset (UPLOAD) + writeNotAllowed(230); + + final int code; + const SyncSessionErrorCode(this.code); } From d0bec7306f143ce7a9bb8c6d3da383a4d2cb504a Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 11:32:49 +0300 Subject: [PATCH 093/122] fix up SyncErrors support --- .vscode/settings.json | 3 +- common/lib/src/realm_types.dart | 106 +++++++++++++++++++++++-------- lib/src/configuration.dart | 64 +++++++++++-------- lib/src/native/realm_core.dart | 109 +++++++++++++------------------- lib/src/realm_class.dart | 15 ++++- lib/src/session.dart | 10 +-- test/session_test.dart | 45 +++++++------ 7 files changed, 205 insertions(+), 147 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 956f2ebde..0e776fefc 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -22,6 +22,7 @@ }, "files.associations": { "filesystem": "cpp", - "*.ipp": "cpp" + "*.ipp": "cpp", + "system_error": "cpp" }, } \ No newline at end of file diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index dc6e1263e..79f8b238c 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -111,57 +111,108 @@ class RealmAny { /// Thrown when an error occurs during synchronization /// {@category Sync} -class SyncError extends RealmError { - /// The code indicating the type of the sync error. - final T code; +class SyncError extends RealmError { + /// The numeric code value indicating the type of the sync error. + final int codeValue; /// The category of the sync error final SyncErrorCategory category; - SyncError(String message, this.category, this.code) : super(message); + SyncError(String message, this.category, this.codeValue) : super(message); - static SyncError create(String message, SyncErrorCategory category, int code) { + /// Creates a specific type of [SyncError] instance based on the [category] and the [code] supplied. + static SyncError create(String message, SyncErrorCategory category, int code, {bool isFatal = false}) { switch (category) { case SyncErrorCategory.client: - return SyncError(message, category, SyncClientErrorCode.values[code]); + final errorCode = SyncClientErrorCode.values[code]; + if (errorCode == SyncClientErrorCode.autoClientResetFailure) { + return SyncClientResetError(message); + } + return SyncClientError(message, category, errorCode, isFatal: isFatal); case SyncErrorCategory.connection: - return SyncError(message, category, SyncConnectionErrorCode.values[code]); + return SyncConnectionError(message, category, SyncConnectionErrorCode.values[code], isFatal: isFatal); case SyncErrorCategory.session: - return SyncError(message, category, SyncSessionErrorCode.values[code]); + return SyncSessionError(message, category, SyncSessionErrorCode.values[code], isFatal: isFatal); case SyncErrorCategory.system: case SyncErrorCategory.unknown: default: - return SyncError(message, category, SyncGeneralErrorCode.generalError); + return GeneralSyncError(message, category, code); } } -} -/// A general or unknown sync error -class GeneralSyncError extends SyncError { - @override - final int generalError; - - GeneralSyncError(String message, SyncErrorCategory category, this.code) : super(message, category, SyncGeneralErrorCode.generalError); + /// As a specific [SyncError] type. + T as() => this as T; } /// An error type that describes a session-level error condition. /// {@category Sync} -class SessionError extends SyncError { +class SyncClientError extends SyncError { /// If true the received error is fatal. final bool isFatal; - SessionError( + /// The [SyncClientErrorCode] value indicating the type of the sync error. + SyncClientErrorCode get code => SyncClientErrorCode.values[codeValue]; + + SyncClientError( String message, SyncErrorCategory category, - T code, { + SyncClientErrorCode errorCode, { this.isFatal = false, - }) : super(message, category, code); + }) : super(message, category, errorCode.index); } /// An error type that describes a client reset error condition. /// {@category Sync} -class ClientResetError extends SessionError { - ClientResetError(String message) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure, isFatal: true); +class SyncClientResetError extends SyncError { + /// If true the received error is fatal. + final bool isFatal = true; + + /// The [ClientResetError] has error code of [SyncClientErrorCode.autoClientResetFailure] + SyncClientErrorCode get code => SyncClientErrorCode.autoClientResetFailure; + + SyncClientResetError(String message) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.index); +} + +/// An error type that describes a connection-level error condition. +/// {@category Sync} +class SyncConnectionError extends SyncError { + /// If true the received error is fatal. + final bool isFatal; + + /// The [SyncConnectionErrorCode] value indicating the type of the sync error. + SyncConnectionErrorCode get code => SyncConnectionErrorCode.values[codeValue]; + + SyncConnectionError( + String message, + SyncErrorCategory category, + SyncConnectionErrorCode errorCode, { + this.isFatal = false, + }) : super(message, category, errorCode.index); +} + +/// An error type that describes a session-level error condition. +/// {@category Sync} +class SyncSessionError extends SyncError { + /// If true the received error is fatal. + final bool isFatal; + + /// The [SyncSessionErrorCode] value indicating the type of the sync error. + SyncSessionErrorCode get code => SyncSessionErrorCode.values[codeValue]; + + SyncSessionError( + String message, + SyncErrorCategory category, + SyncSessionErrorCode errorCode, { + this.isFatal = false, + }) : super(message, category, errorCode.index); +} + +/// A general or unknown sync error +class GeneralSyncError extends SyncError { + /// The numeric value indicating the type of the general sync error. + int get code => codeValue; + + GeneralSyncError(String message, SyncErrorCategory category, int code) : super(message, category, code); } /// The category of a [SyncError]. @@ -182,8 +233,13 @@ enum SyncErrorCategory { unknown, } -enum SyncGeneralErrorCode { - generalError(999) +/// General sync error codes +enum GeneralSyncErrorCode { + // A general sync error code + unknown(9999); + + final int code; + const GeneralSyncErrorCode(this.code); } /// Protocol errors discovered by the client. @@ -338,7 +394,7 @@ enum SyncConnectionErrorCode { /// Connected with wrong wire protocol - should switch to PBS switchToPbs(114); - + final int code; const SyncConnectionErrorCode(this.code); } diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 781ff2a13..e7c8f8006 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -44,16 +44,6 @@ typedef ShouldCompactCallback = bool Function(int totalSize, int usedSize); /// Realms, even if all objects in the Realm are deleted. typedef InitialDataCallback = void Function(Realm realm); -///The signature of a callback that will be invoked whenever a [SessionError] occurs for the synchronized Realm. -/// -/// Client reset errors will not be reported through this callback as they are handled by [ClientResetHandler]. -typedef SessionErrorHandler = void Function(SessionError error); - -/// The signature of a callback that will be invoked if a client reset error occurs for this [Realm]. -/// -/// Currently, Flexible sync only supports the Manual Recovery. -typedef ClientResetHandler = void Function(SessionError error); - /// Configuration used to create a [Realm] instance /// {@category Configuration} abstract class Configuration { @@ -69,11 +59,7 @@ abstract class Configuration { static String? _defaultPath; - Configuration._( - List schemaObjects, { - String? path, - this.fifoFilesFallbackPath - }) : schema = RealmSchema(schemaObjects) { + Configuration._(List schemaObjects, {String? path, this.fifoFilesFallbackPath}) : schema = RealmSchema(schemaObjects) { this.path = _getPath(path); } @@ -146,16 +132,16 @@ abstract class Configuration { List schemaObjects, { String? fifoFilesFallbackPath, String? path, - SessionErrorHandler? sessionErrorHandler, - ClientResetHandler? clientResetHandler, + SyncErrorHandler? syncErrorHandler, + SyncClientResetErrorHandler? syncClientResetErrorHandler, }) => FlexibleSyncConfiguration._( user, schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, path: path, - sessionErrorHandler: sessionErrorHandler, - clientResetHandler: clientResetHandler, + syncErrorHandler: syncErrorHandler, + syncClientResetErrorHandler: syncClientResetErrorHandler, ); } @@ -230,19 +216,19 @@ class FlexibleSyncConfiguration extends Configuration { SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; - /// Called when a [SessionError] occurs for the synchronized [Realm]. - final SessionErrorHandler? sessionErrorHandler; + /// Called when a [SyncError] occurs for this synchronized [Realm]. + final SyncErrorHandler? syncErrorHandler; - /// Called when a [ClientResetError] occurs for this [Realm] - final ClientResetHandler? clientResetHandler; + /// Called when a [SyncClientResetError] occurs for this synchronized [Realm] + final SyncClientResetErrorHandler? syncClientResetErrorHandler; FlexibleSyncConfiguration._( this.user, List schemaObjects, { String? fifoFilesFallbackPath, String? path, - this.sessionErrorHandler, - this.clientResetHandler, + this.syncErrorHandler, + this.syncClientResetErrorHandler, }) : super._( schemaObjects, fifoFilesFallbackPath: fifoFilesFallbackPath, @@ -318,3 +304,31 @@ class RealmSchema extends Iterable { @override SchemaObject elementAt(int index) => _schema.elementAt(index); } + +///The signature of a callback that will be invoked whenever a [SyncError] occurs for the synchronized Realm. +/// +/// Client reset errors will not be reported through this callback as they are handled by [ClientResetHandler]. +class SyncErrorHandler { + /// The callback that handles the [SyncError]. + void Function(SyncError code) callback; + + /// Invokes the [SyncError] handling [callback]. + void call(SyncError error) => callback(error); + + /// Initializes a new instance of of [SyncErrorHandler]. + SyncErrorHandler(this.callback); +} + +/// The signature of a callback that will be invoked if a client reset error occurs for this [Realm]. +/// +/// Currently, Flexible sync only supports the Manual Recovery. +class SyncClientResetErrorHandler { + /// The callback that handles the [SyncClientResetError]. + void Function(SyncClientResetError code) callback; + + /// Invokes the [SyncClientResetError] handling [callback]. + void call(SyncClientResetError error) => callback(error); + + /// Initializes a new instance of of [SyncClientResetErrorHandler]. + SyncClientResetErrorHandler(this.callback); +} diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index ebe16fe74..e910299c5 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -15,6 +15,8 @@ // limitations under the License. // //////////////////////////////////////////////////////////////////////////////// +// ignore_for_file: non_constant_identifier_names + import 'dart:async'; import 'dart:convert'; import 'dart:ffi'; @@ -44,6 +46,9 @@ late RealmLibrary _realmLib; final _RealmCore realmCore = _RealmCore(); +const int TRUE = 1; +const int FALSE = 0; + class _RealmCore { // From realm.h. Currently not exported from the shared library static const int RLM_INVALID_CLASS_KEY = 0x7FFFFFFF; @@ -52,9 +57,6 @@ class _RealmCore { // ignore: unused_field static const int RLM_INVALID_OBJECT_KEY = -1; - static const int TRUE = 1; - static const int FALSE = 0; - // Hide the RealmCore class and make it a singleton static _RealmCore? _instance; late final int isolateKey; @@ -188,7 +190,7 @@ class _RealmCore { 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_error_handler(syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), + _realmLib.realm_sync_config_set_error_handler(syncConfigPtr, Pointer.fromFunction(syncErrorHandlerCallback), config.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle); _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { @@ -277,7 +279,7 @@ class _RealmCore { return result == nullptr ? null : SubscriptionHandle._(result); } - static void _stateChangeCallback(Pointer userdata, int state) { + static void stateChangeCallback(Pointer userdata, int state) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; @@ -290,7 +292,7 @@ class _RealmCore { _realmLib.realm_dart_sync_on_subscription_set_state_change_async( subscriptions.handle._pointer, notifyWhen.index, - Pointer.fromFunction(_stateChangeCallback), + Pointer.fromFunction(stateChangeCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -403,18 +405,20 @@ class _RealmCore { return config.shouldCompactCallback!(totalSize, usedSize) ? TRUE : FALSE; } - static void _syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { - print(error.detailed_message.cast().toRealmDartString()!); + static void syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { final FlexibleSyncConfiguration? syncConfig = userdata.toObject(isPersistent: true); if (syncConfig == null) { return; } - final sessionError = error.toSessionError(); - if (error.is_client_reset_requested == 0 && syncConfig.sessionErrorHandler != null) { - syncConfig.sessionErrorHandler!(sessionError); - } - if (error.is_client_reset_requested == 1 && syncConfig.clientResetHandler != null) { - syncConfig.clientResetHandler!(ClientResetError(sessionError.message!, sessionError.category, isFatal: sessionError.isFatal)); + + final syncError = error.toSyncError(); + + if (syncError is SyncClientResetError) { + if (syncConfig.syncClientResetErrorHandler != null) { + syncConfig.syncClientResetErrorHandler!(syncError); + } + } else if (syncConfig.syncErrorHandler != null) { + syncConfig.syncErrorHandler!(syncError); } } @@ -1049,7 +1053,7 @@ class _RealmCore { }); } - static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { + static void logCallback(Pointer userdata, int levelAsInt, Pointer message) { try { final logger = Realm.logger; final level = _LogLevel.values[levelAsInt].loggerLevel; @@ -1069,16 +1073,16 @@ class _RealmCore { _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toUtf8Ptr(arena)); _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); - + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, _LogLevel.fromLevel(Realm.logger.level).index); _realmLib.realm_dart_sync_client_config_set_log_callback( handle._pointer, - Pointer.fromFunction(_logCallback), + Pointer.fromFunction(logCallback), nullptr, nullptr, scheduler.handle._pointer, ); - + _realmLib.realm_sync_client_config_set_connect_timeout(handle._pointer, configuration.maxConnectionTimeout.inMicroseconds); if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { _realmLib.realm_sync_client_config_set_metadata_encryption_key(handle._pointer, configuration.metadataEncryptionKey!.toUint8Ptr(arena)); @@ -1100,7 +1104,7 @@ class _RealmCore { return _realmLib.realm_app_get_app_id(app.handle._pointer).cast().toRealmDartString()!; } - static void _app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { + static void app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { final Completer? completer = userdata.toObject(isPersistent: true); if (completer == null) { return; @@ -1127,7 +1131,7 @@ class _RealmCore { () => _realmLib.realm_app_log_in_with_credentials( app.handle._pointer, credentials.handle._pointer, - Pointer.fromFunction(_app_user_completion_callback), + Pointer.fromFunction(app_user_completion_callback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1262,7 +1266,7 @@ class _RealmCore { return UserHandle._(userPtr); } - static void _logOutCallback(Pointer userdata, Pointer error) { + static void logOutCallback(Pointer userdata, Pointer error) { final Completer? completer = userdata.toObject(isPersistent: true); if (completer == null) { return; @@ -1283,7 +1287,7 @@ class _RealmCore { _realmLib.invokeGetBool( () => _realmLib.realm_app_log_out_current_user( application.handle._pointer, - Pointer.fromFunction(_logOutCallback), + Pointer.fromFunction(logOutCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1293,7 +1297,7 @@ class _RealmCore { () => _realmLib.realm_app_log_out( application.handle._pointer, user.handle._pointer, - Pointer.fromFunction(_logOutCallback), + Pointer.fromFunction(logOutCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1381,7 +1385,7 @@ class _RealmCore { app.handle._pointer, user.handle._pointer, credentials.handle._pointer, - Pointer.fromFunction(_app_user_completion_callback), + Pointer.fromFunction(app_user_completion_callback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1518,7 +1522,7 @@ class _RealmCore { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_upload_completion( session.handle._pointer, - Pointer.fromFunction(_waitCompletionCallback), + Pointer.fromFunction(sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -1530,7 +1534,7 @@ class _RealmCore { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_download_completion( session.handle._pointer, - Pointer.fromFunction(_waitCompletionCallback), + Pointer.fromFunction(sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -1538,7 +1542,7 @@ class _RealmCore { return completer.future; } - static void _waitCompletionCallback(Pointer userdata, Pointer errorCode) { + static void sessionWaitCompletionCallback(Pointer userdata, Pointer errorCode) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; @@ -1904,24 +1908,25 @@ extension on Pointer { } extension on realm_sync_error { - SessionError toSessionError() { - final messageText = detailed_message.cast().toRealmDartString()!; - final SyncErrorCategory errorCategory = SyncErrorCategory.values[error_code.category]; + SyncError toSyncError() { + final message = detailed_message.cast().toRealmDartString()!; + final SyncErrorCategory category = SyncErrorCategory.values[error_code.category]; final isFatal = is_fatal == 0 ? false : true; + final bool isClientResetRequested = is_client_reset_requested == TRUE; - return SessionError( - messageText, - errorCategory, - isFatal: isFatal, - code: error_code.value, - ); + //client reset can be requested with is_client_reset_requested disregarding the error_code.value + if (isClientResetRequested) { + return SyncClientResetError(message); + } + + return SyncError.create(message, category, error_code.value, isFatal: isFatal); } } extension on Pointer { SyncError toSyncError() { - final message = ref.message.cast().toRealmDartString()!; - return SyncError(message, SyncErrorCategory.values[ref.category], ref.value); + final message = ref.message.cast().toDartString(); + return SyncError.create(message, SyncErrorCategory.values[ref.category], ref.value, isFatal: false); } } @@ -1955,8 +1960,6 @@ extension on List { } } -// TODO: Once enhanced-enums land in 2.17, replace with: -/* enum _CustomErrorCode { noError(0), httpClientDisposed(997), @@ -1967,32 +1970,6 @@ enum _CustomErrorCode { final int code; const _CustomErrorCode(this.code); } -*/ - -enum _CustomErrorCode { - noError, - httpClientDisposed, - unknownHttp, - unknown, - timeout, -} - -extension on _CustomErrorCode { - int get code { - switch (this) { - case _CustomErrorCode.noError: - return 0; - case _CustomErrorCode.httpClientDisposed: - return 997; - case _CustomErrorCode.unknownHttp: - return 998; - case _CustomErrorCode.unknown: - return 999; - case _CustomErrorCode.timeout: - return 1000; - } - } -} enum _HttpMethod { get, diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 9983af6dc..9b2204277 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -38,10 +38,17 @@ export 'package:realm_common/realm_common.dart' MapTo, PrimaryKey, RealmError, - ClientResetError, - SessionError, SyncError, + SyncClientError, + SyncClientResetError, + SyncConnectionError, + SyncSessionError, + GeneralSyncError, SyncErrorCategory, + GeneralSyncErrorCode, + SyncClientErrorCode, + SyncConnectionErrorCode, + SyncSessionErrorCode, RealmModel, RealmUnsupportedSetError, RealmStateError, @@ -61,7 +68,9 @@ export "configuration.dart" LocalConfiguration, RealmSchema, SchemaObject, - ShouldCompactCallback; + ShouldCompactCallback, + SyncErrorHandler, + SyncClientResetErrorHandler; export 'credentials.dart' show Credentials, AuthProviderType, EmailPasswordAuthProvider; export 'list.dart' show RealmList, RealmListOfObject, RealmListChanges; diff --git a/lib/src/session.dart b/lib/src/session.dart index 3f89813b9..9e2c5f6fc 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -67,10 +67,6 @@ class Session { final controller = SessionProgressNotificationsController(this, direction, mode); return controller.createStream(); } - - void _raiseSessionError(SyncErrorCategory category, int errorCode, bool isFatal) { - realmCore.raiseError(this, category, errorCode, isFatal); - } } /// The current state of a [Session] object @@ -135,11 +131,9 @@ extension SessionInternal on Session { static Session create(SessionHandle handle) => Session._(handle); SessionHandle get handle => _handle; -} -extension SessionDevInternal on Session { - void raiseSessionError(SyncErrorCategory category, int errorCode, bool isFatal) { - _raiseSessionError(category, errorCode, isFatal); + void raiseError(SyncErrorCategory category, int errorCode, bool isFatal) { + realmCore.raiseError(this, category, errorCode, isFatal); } } diff --git a/test/session_test.dart b/test/session_test.dart index 4f9b815b3..29d50603b 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -21,7 +21,7 @@ import 'dart:io'; import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; -import '../lib/src/session.dart' show SessionDevInternal; +import '../lib/src/session.dart' show SessionInternal; import 'test.dart'; Future main([List? args]) async { @@ -279,43 +279,50 @@ Future main([List? args]) async { baasTest('SyncSession test error handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { + final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: SyncErrorHandler((syncError) { + expect(syncError, isA()); + final sessionError = syncError.as(); expect(sessionError.category, SyncErrorCategory.session); expect(sessionError.isFatal, false); - expect(sessionError.code, 100); + expect(sessionError.code, SyncSessionErrorCode.badAuthentication); expect(sessionError.message, "Simulated session error"); - }); + })); + final realm = getRealm(config); - realm.syncSession.raiseSessionError(SyncErrorCategory.session, 100, false); + realm.syncSession.raiseError(SyncErrorCategory.session, SyncSessionErrorCode.badAuthentication.index, false); }); baasTest('SyncSession test fatal error handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], sessionErrorHandler: (sessionError) { - expect(sessionError.category, SyncErrorCategory.client); - expect(sessionError.isFatal, true); - expect(sessionError.code, 111); - expect(sessionError.message, "Simulated client error"); - }); + final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: SyncErrorHandler((syncError) { + expect(syncError, isA()); + final syncClientError = syncError.as(); + expect(syncClientError.category, SyncErrorCategory.client); + expect(syncClientError.isFatal, true); + expect(syncClientError.code, SyncClientErrorCode.badChangeset); + expect(syncClientError.message, "Simulated client error"); + })); final realm = getRealm(config); - realm.syncSession.raiseSessionError(SyncErrorCategory.client, 111, true); + realm.syncSession.raiseError(SyncErrorCategory.client, SyncClientErrorCode.badChangeset.index, true); }); baasTest('SyncSession client reset handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], clientResetHandler: (clientResetError) { - expect(clientResetError.category, SyncErrorCategory.session); - expect(clientResetError.isFatal, true); - expect(clientResetError.code, 132); // 132: ClientError.auto_client_reset_failure - expect(clientResetError.message, "Simulated session error"); - }); + final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: SyncErrorHandler((syncError) { + expect(syncError, isA()); + final syncClientResetError = syncError.as(); + expect(syncClientResetError.category, SyncErrorCategory.session); + expect(syncClientResetError.isFatal, true); + expect(syncClientResetError.code, SyncClientErrorCode.autoClientResetFailure); + expect(syncClientResetError.message, "Simulated session error"); + })); final realm = getRealm(config); - realm.syncSession.raiseSessionError(SyncErrorCategory.session, 132, true); + realm.syncSession.raiseError(SyncErrorCategory.session, SyncClientErrorCode.autoClientResetFailure.index, true); }); } From 9d8d702b6dc6131e38b1a544274b4284e683749a Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 12:05:24 +0300 Subject: [PATCH 094/122] fix changelog --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85c4afa9f..0ed7c3167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,10 +40,11 @@ * Support user profile data. ([#570](https://github.com/realm/realm-dart/pull/570)) * Support flexible synchronization. ([#496](https://github.com/realm/realm-dart/pull/496)) * Added support for DateTime properties. ([#569](https://github.com/realm/realm-dart/pull/569)) -* Support SessionErrorHandler in FlexibleSyncConfiguration. ([#577](https://github.com/realm/realm-dart/pull/577)) -* Support ClientResetHandler in FlexibleSyncConfiguration. ([#608](https://github.com/realm/realm-dart/pull/608)) * Support setting logger on AppConfiguration. ([#583](https://github.com/realm/realm-dart/pull/583)) * Support setting logger on Realm class. Default is to print info message or worse to the console. ([#583](https://github.com/realm/realm-dart/pull/583)) +* Support SyncErrorHandler in FlexibleSyncConfiguration. ([#577](https://github.com/realm/realm-dart/pull/577)) +* Support SyncClientResetHandler in FlexibleSyncConfiguration. ([#608](https://github.com/realm/realm-dart/pull/608)) + ### Fixed * Fixed an issue that would result in the wrong transaction being rolled back if you start a write transaction inside a write transaction. ([#442](https://github.com/realm/realm-dart/issues/442)) From 1f30e7d07ef4256cbdde57235a8ae9d5fc04b960 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 13:30:33 +0300 Subject: [PATCH 095/122] disable logging by default --- .vscode/settings.json | 20 ++++- lib/src/native/realm_core.dart | 127 +++++++++++++++++++---------- lib/src/realm_class.dart | 143 +++++++++++++++------------------ 3 files changed, 169 insertions(+), 121 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 0e776fefc..ceb8d0a4e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -23,6 +23,24 @@ "files.associations": { "filesystem": "cpp", "*.ipp": "cpp", - "system_error": "cpp" + "system_error": "cpp", + "algorithm": "cpp", + "atomic": "cpp", + "chrono": "cpp", + "compare": "cpp", + "concepts": "cpp", + "format": "cpp", + "fstream": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "optional": "cpp", + "variant": "cpp", + "vector": "cpp", + "xlocale": "cpp", + "xlocmon": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xutility": "cpp" }, } \ No newline at end of file diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index e910299c5..b30079aea 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -986,22 +986,22 @@ class _RealmCore { late HttpClientRequest request; // this throws if requestMethod is unknown _HttpMethod - final method = _HttpMethod.values[requestMethod]; + final method = HttpMethod.values[requestMethod]; switch (method) { - case _HttpMethod.delete: + case HttpMethod.delete: request = await client.deleteUrl(url); break; - case _HttpMethod.put: + case HttpMethod.put: request = await client.putUrl(url); break; - case _HttpMethod.patch: + case HttpMethod.patch: request = await client.patchUrl(url); break; - case _HttpMethod.post: + case HttpMethod.post: request = await client.postUrl(url); break; - case _HttpMethod.get: + case HttpMethod.get: request = await client.getUrl(url); break; } @@ -1039,14 +1039,14 @@ class _RealmCore { } }); - responseRef.custom_status_code = _CustomErrorCode.noError.code; + responseRef.custom_status_code = CustomErrorCode.noError.code; } on SocketException catch (_) { // TODO: A Timeout causes a socket exception, but not all socket exceptions are due to timeouts - responseRef.custom_status_code = _CustomErrorCode.timeout.code; + responseRef.custom_status_code = CustomErrorCode.timeout.code; } on HttpException catch (_) { - responseRef.custom_status_code = _CustomErrorCode.unknownHttp.code; + responseRef.custom_status_code = CustomErrorCode.unknownHttp.code; } catch (_) { - responseRef.custom_status_code = _CustomErrorCode.unknown.code; + responseRef.custom_status_code = CustomErrorCode.unknown.code; } finally { _realmLib.realm_http_transport_complete_request(request_context, response_pointer); } @@ -1054,16 +1054,20 @@ class _RealmCore { } static void logCallback(Pointer userdata, int levelAsInt, Pointer message) { + final logger = Realm.logger; + if (logger == null) { + return; + } + try { - final logger = Realm.logger; - final level = _LogLevel.values[levelAsInt].loggerLevel; + final level = LevelExt.fromInt(levelAsInt); - // Don't do expensive utf8 to utf16 conversion unless we have to.. + // Don't do expensive utf8 to utf16 conversion unless needed. if (logger.isLoggable(level)) { logger.log(level, message.cast().toDartString()); } } finally { - _realmLib.realm_free(message.cast()); // .. but always free the message + _realmLib.realm_free(message.cast()); // always free the message. } } @@ -1074,14 +1078,19 @@ class _RealmCore { _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toUtf8Ptr(arena)); _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); - _realmLib.realm_sync_client_config_set_log_level(handle._pointer, _LogLevel.fromLevel(Realm.logger.level).index); - _realmLib.realm_dart_sync_client_config_set_log_callback( - handle._pointer, - Pointer.fromFunction(logCallback), - nullptr, - nullptr, - scheduler.handle._pointer, - ); + if (Realm.logger != null) { + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, Realm.logger!.level.toInt()); + + _realmLib.realm_dart_sync_client_config_set_log_callback( + handle._pointer, + Pointer.fromFunction(logCallback), + nullptr, + nullptr, + scheduler.handle._pointer, + ); + } else { + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, RealmLogLevel.off.toInt()); + } _realmLib.realm_sync_client_config_set_connect_timeout(handle._pointer, configuration.maxConnectionTimeout.inMicroseconds); if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { @@ -1960,18 +1969,19 @@ extension on List { } } -enum _CustomErrorCode { +enum CustomErrorCode { noError(0), + // ignore: unused_field httpClientDisposed(997), unknownHttp(998), unknown(999), timeout(1000); final int code; - const _CustomErrorCode(this.code); + const CustomErrorCode(this.code); } -enum _HttpMethod { +enum HttpMethod { get, post, patch, @@ -2009,25 +2019,54 @@ extension on realm_object_id { } } -// Helper enum for converting Level -enum _LogLevel { - all(RealmLogLevel.all), - trace(RealmLogLevel.trace), - debug(RealmLogLevel.debug), - detail(RealmLogLevel.detail), - info(RealmLogLevel.info), - warn(RealmLogLevel.warn), - error(RealmLogLevel.error), - fatal(RealmLogLevel.fatal), - off(RealmLogLevel.off); - - final Level loggerLevel; - const _LogLevel(this.loggerLevel); - - factory _LogLevel.fromLevel(Level level) { - for (final candidate in _LogLevel.values) { - if (level.value > candidate.loggerLevel.value) return candidate; +extension LevelExt on Level { + int toInt() { + if (this == Level.ALL) { + return 0; + } else if (name == "TRACE") { + return 1; + } else if (name == "DEBUG") { + return 2; + } else if (name == "DETAIL") { + return 3; + } else if (this == Level.INFO) { + return 4; + } else if (this == Level.WARNING) { + return 5; + } else if (name == "ERROR") { + return 6; + } else if (name == "FATAL") { + return 7; + } else if (this == Level.OFF) { + return 8; + } else { + // if unknown logging is off + return 8; } - return _LogLevel.off; + } + + static Level fromInt(int value) { + if (value == 0) { + return RealmLogLevel.all; + } else if (value == 1) { + return RealmLogLevel.trace; + } else if (value == 2) { + return RealmLogLevel.debug; + } else if (value == 3) { + return RealmLogLevel.detail; + } else if (value == 4) { + return RealmLogLevel.info; + } else if (value == 5) { + return RealmLogLevel.warn; + } else if (value == 6) { + return RealmLogLevel.error; + } else if (value == 7) { + return RealmLogLevel.fatal; + } else if (value == 8) { + return RealmLogLevel.off; + } + + // if unknown logging is off + return RealmLogLevel.off; } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 9b2204277..d0119d44d 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -81,72 +81,6 @@ export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetSt export 'user.dart' show User, UserState, UserIdentity; export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress; -/// Specifies the criticality level above which messages will be logged -/// by the default sync client logger. -/// {@category Realm} -class RealmLogLevel { - /// Log everything. This will seriously harm the performance of the - /// sync client and should never be used in production scenarios. - /// - /// Same as [Level.ALL] - static const all = Level.ALL; - - /// A version of [debug] that allows for very high volume output. - /// This may seriously affect the performance of the sync client. - /// - /// Same as [Level.FINEST] - static const trace = Level('TRACE', 300); - - /// Reveal information that can aid debugging, no longer paying - /// attention to efficiency. - /// - /// Same as [Level.FINER] - static const debug = Level('DEBUG', 400); - - /// Same as [info], but prioritize completeness over minimalism. - /// - /// Same as [Level.FINE]; - static const detail = Level('DETAIL', 500); - - /// Log operational sync client messages, but in a minimalist fashion to - /// avoid general overhead from logging and to keep volume down. - /// - /// Same as [Level.INFO]; - static const info = Level.INFO; - - /// Log errors and warnings. - /// - /// Same as [Level.WARNING]; - static const warn = Level.WARNING; - - /// Log errors only. - /// - /// Same as [Level.SEVERE]; - static const error = Level('ERROR', 1000); - - /// Log only fatal errors. - /// - /// Same as [Level.SHOUT]; - static const fatal = Level('FATAL', 1200); - - /// Log nothing. - /// - /// Same as [Level.OFF]; - static const off = Level.OFF; - - static const levels = [ - all, - trace, - debug, - detail, - info, - warn, - error, - fatal, - off, - ]; -} - /// A [Realm] instance represents a `Realm` database. /// /// {@category Realm} @@ -154,16 +88,6 @@ class Realm { final Map _metadata = {}; final RealmHandle _handle; - /// The logger to use. - /// - /// Defaults to printing info or worse to the console - static late var logger = Logger.detached('Realm') - ..level = RealmLogLevel.info - ..onRecord.listen((event) => print(event)); - - /// Shutdown. - static void shutdown() => scheduler.stop(); - /// The [Configuration] object used to open this [Realm] final Configuration config; @@ -386,6 +310,19 @@ class Realm { if (other is! Realm) return false; return realmCore.realmEquals(this, other); } + + /// The logger to use for logging or null. + /// + /// To enable logging assign a logger instance and recreate any [FlexibleSyncConfiguration] configuraitons and reopen the synced [Realm]s. + static Logger? logger; + + /// This default logger logs to the console with [RealmLogLevel.info] level. + static Logger get defaultLogger => Logger.detached('Realm') + ..level = RealmLogLevel.info + ..onRecord.listen((event) => print(event)); + + /// Shutdown. + static void shutdown() => scheduler.stop(); } /// @nodoc @@ -474,3 +411,57 @@ abstract class NotificationsController { handle = null; } } + +/// Specifies the criticality level above which messages will be logged +/// by the default sync client logger. +/// {@category Realm} +class RealmLogLevel { + /// Log everything. This will seriously harm the performance of the + /// sync client and should never be used in production scenarios. + /// + /// Same as [Level.ALL] + static const all = Level.ALL; + + /// A version of [debug] that allows for very high volume output. + /// This may seriously affect the performance of the sync client. + /// + /// Same as [Level.FINEST] + static const trace = Level('TRACE', 300); + + /// Reveal information that can aid debugging, no longer paying + /// attention to efficiency. + /// + /// Same as [Level.FINER] + static const debug = Level('DEBUG', 400); + + /// Same as [info], but prioritize completeness over minimalism. + /// + /// Same as [Level.FINE]; + static const detail = Level('DETAIL', 500); + + /// Log operational sync client messages, but in a minimalist fashion to + /// avoid general overhead from logging and to keep volume down. + /// + /// Same as [Level.INFO]; + static const info = Level.INFO; + + /// Log errors and warnings. + /// + /// Same as [Level.WARNING]; + static const warn = Level.WARNING; + + /// Log errors only. + /// + /// Same as [Level.SEVERE]; + static const error = Level('ERROR', 1000); + + /// Log only fatal errors. + /// + /// Same as [Level.SHOUT]; + static const fatal = Level('FATAL', 1200); + + /// Turn off logging. + /// + /// Same as [Level.OFF]; + static const off = Level.OFF; +} From 7855b410c639e3f0dded52413582e55eed072d5b Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 13:39:32 +0300 Subject: [PATCH 096/122] fix app tests --- test/app_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/app_test.dart b/test/app_test.dart index 87ad58b37..fe132ef74 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -195,7 +195,7 @@ Future main([List? args]) async { await testLogger( configuration, - Realm.logger, + Realm.logger!, maxExpectedCounts: { // No problems expected! RealmLogLevel.fatal: 0, From f079ec2530390d776f36006c16914e44695da41e Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 13:57:11 +0300 Subject: [PATCH 097/122] refactor waitForCondition --- test/test.dart | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/test/test.dart b/test/test.dart index 3db41fa7f..a230752d8 100644 --- a/test/test.dart +++ b/test/test.dart @@ -374,16 +374,22 @@ Future loginWithRetry(App app, Credentials credentials, {int retryCount = } } -Future waitForCondition(bool Function() condition, - {Duration timeout = const Duration(seconds: 1), Duration retryDelay = const Duration(milliseconds: 100), String? message}) async { - final start = DateTime.now(); - while (!condition()) { - if (DateTime.now().difference(start) > timeout) { - throw TimeoutException('Condition not met within $timeout${message != null ? ': $message' : ''}'); - } - - await Future.delayed(retryDelay); - } +Future waitForCondition( + bool Function() condition, { + Duration timeout = const Duration(seconds: 1), + Duration retryDelay = const Duration(milliseconds: 100), + String? message, +}) async { + await Future.any([ + Future.delayed(timeout, () => throw TimeoutException('Condition not met within $timeout. Message: ${message != null ? ': $message' : ''}')), + Future.doWhile(() async { + if (condition()) { + return false; + } + await Future.delayed(retryDelay); + return true; + }) + ]); } extension RealmObjectTest on RealmObject { From e4bb6770d1565bd7087aaa4bb4b95f41612907fc Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 15:09:07 +0300 Subject: [PATCH 098/122] await more for flaky "flexible sync roundtrip" test --- test/subscription_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 8a5622259..cd8feb41d 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -482,6 +482,8 @@ Future main([List? args]) async { await realmY.syncSession.waitForUpload(); await realmY.syncSession.waitForDownload(); + await Future.delayed(Duration(seconds: 2)); + waitForCondition(() { final task = realmY.find(objectId); return task != null; From 67485dd37f91cb5aecdb5552f8ad4ec1bfcce4d4 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 15:31:18 +0300 Subject: [PATCH 099/122] use path --- test/subscription_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index cd8feb41d..5c2762072 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -460,7 +460,9 @@ Future main([List? args]) async { final userY = await appY.logIn(credentials); final realmX = getRealm(Configuration.flexibleSync(userX, [Task.schema])); - final realmY = getRealm(Configuration.flexibleSync(userY, [Task.schema])); + + final pathY = path.join(temporaryDir.path, "Y.realm"); + final realmY = getRealm(Configuration.flexibleSync(userY, [Task.schema], path: pathY)); realmX.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realmX.all()); From 59ae533606abffdfac89138586363fee0a97e0eb Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 15:53:33 +0300 Subject: [PATCH 100/122] fixup build_resolvers dep to not use buggy 2.0.9 package. --- generator/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/pubspec.yaml b/generator/pubspec.yaml index 45cfe6399..b07bea15c 100644 --- a/generator/pubspec.yaml +++ b/generator/pubspec.yaml @@ -16,7 +16,7 @@ environment: dependencies: analyzer: ^4.0.0 - build_resolvers: ^2.0.0 + build_resolvers: ">=2.0.0 <= 2.0.8" build: ^2.0.0 dart_style: ^2.2.0 realm_common: From 8e339848356bf36e8fe2726ec25cb591c118f822 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 16:01:36 +0300 Subject: [PATCH 101/122] fixup build_resolvers dep to not use buggy 2.0.9 package. (cherry picked from commit 59ae533606abffdfac89138586363fee0a97e0eb) --- generator/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generator/pubspec.yaml b/generator/pubspec.yaml index 45cfe6399..b07bea15c 100644 --- a/generator/pubspec.yaml +++ b/generator/pubspec.yaml @@ -16,7 +16,7 @@ environment: dependencies: analyzer: ^4.0.0 - build_resolvers: ^2.0.0 + build_resolvers: ">=2.0.0 <= 2.0.8" build: ^2.0.0 dart_style: ^2.2.0 realm_common: From 71f6897fd3c82305d2b376d390498a74356cd0dc Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 16:27:41 +0300 Subject: [PATCH 102/122] disable flexible sync roundtrip test --- test/subscription_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 5c2762072..ee02d4de9 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -493,5 +493,5 @@ Future main([List? args]) async { final task = realmY.find(objectId); expect(task, isNotNull); - }); + }, skip: "This test is super flaky. Disabling for now."); } From a35602eeef4fc91298eed970d0c226ce7e410bb2 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 16:29:51 +0300 Subject: [PATCH 103/122] try to fix flex syn roundtrip --- test/subscription_test.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index ee02d4de9..8137bb0e9 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -467,6 +467,8 @@ Future main([List? args]) async { realmX.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realmX.all()); }); + + await realmX.subscriptions.waitForSynchronization(); final objectId = ObjectId(); realmX.write(() => realmX.add(Task(objectId))); @@ -474,8 +476,7 @@ Future main([List? args]) async { realmY.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realmY.all()); }); - - await realmX.subscriptions.waitForSynchronization(); + await realmY.subscriptions.waitForSynchronization(); await realmX.syncSession.waitForUpload(); @@ -493,5 +494,5 @@ Future main([List? args]) async { final task = realmY.find(objectId); expect(task, isNotNull); - }, skip: "This test is super flaky. Disabling for now."); + }); //, skip: "This test is super flaky. Disabling for now." } From 08aefade820c9bdeb3cf6b0f48ec4bd7334e8b17 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 16:57:40 +0300 Subject: [PATCH 104/122] fix "flexible sync roundtrip" test --- test/subscription_test.dart | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 8137bb0e9..1682848b0 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -460,25 +460,19 @@ Future main([List? args]) async { final userY = await appY.logIn(credentials); final realmX = getRealm(Configuration.flexibleSync(userX, [Task.schema])); - - final pathY = path.join(temporaryDir.path, "Y.realm"); - final realmY = getRealm(Configuration.flexibleSync(userY, [Task.schema], path: pathY)); + final realmY = getRealm(Configuration.flexibleSync(userY, [Task.schema])); realmX.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realmX.all()); + mutableSubscriptions.add(realmY.all()); }); - + await realmX.subscriptions.waitForSynchronization(); + await realmY.subscriptions.waitForSynchronization(); final objectId = ObjectId(); realmX.write(() => realmX.add(Task(objectId))); - realmY.subscriptions.update((mutableSubscriptions) { - mutableSubscriptions.add(realmY.all()); - }); - - await realmY.subscriptions.waitForSynchronization(); - await realmX.syncSession.waitForUpload(); await realmX.syncSession.waitForDownload(); @@ -494,5 +488,5 @@ Future main([List? args]) async { final task = realmY.find(objectId); expect(task, isNotNull); - }); //, skip: "This test is super flaky. Disabling for now." + }); } From 8f2b37cdaeaf0b0612400bb5af8e1fd209d7ccdd Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 17:06:02 +0300 Subject: [PATCH 105/122] fix flexible sync roundtrip test --- test/subscription_test.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 1682848b0..595f85c5a 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -474,9 +474,6 @@ Future main([List? args]) async { realmX.write(() => realmX.add(Task(objectId))); await realmX.syncSession.waitForUpload(); - await realmX.syncSession.waitForDownload(); - - await realmY.syncSession.waitForUpload(); await realmY.syncSession.waitForDownload(); await Future.delayed(Duration(seconds: 2)); From ba4110af5b083dd2ac30ea6e07dce341f0674678 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 17:23:29 +0300 Subject: [PATCH 106/122] fixup shutdown api, add changelog, brinback scheduler arguments --- lib/src/app.dart | 3 ++- lib/src/list.dart | 4 ++- lib/src/native/realm_core.dart | 45 +++++++++++++++++----------------- lib/src/realm_class.dart | 7 ++++-- lib/src/realm_object.dart | 4 ++- lib/src/results.dart | 4 ++- lib/src/session.dart | 8 +++--- lib/src/subscription.dart | 4 ++- 8 files changed, 46 insertions(+), 33 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 2ab397b71..1c0acde14 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -19,6 +19,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; +import 'package:realm_dart/src/scheduler.dart'; import '../realm.dart'; import 'configuration.dart'; @@ -117,7 +118,7 @@ class App { String get id => realmCore.appGetId(this); /// Create an app with a particular [AppConfiguration] - App(AppConfiguration configuration) : this._(realmCore.getApp(configuration)); + App(AppConfiguration configuration) : this._(realmCore.getApp(configuration, scheduler.handle)); App._(this._handle); diff --git a/lib/src/list.dart b/lib/src/list.dart index 63a89ec94..0903cf5e2 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -19,6 +19,8 @@ import 'dart:async'; import 'dart:collection' as collection; +import 'package:realm_dart/src/scheduler.dart'; + import 'collections.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; @@ -190,7 +192,7 @@ class ListNotificationsController extends NotificationsControl @override RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeListNotifications(list, this); + return realmCore.subscribeListNotifications(list, this, scheduler.handle); } Stream> createStream() { diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b30079aea..32560165b 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -36,7 +36,6 @@ import '../list.dart'; import '../realm_class.dart'; import '../realm_object.dart'; import '../results.dart'; -import '../scheduler.dart'; import '../subscription.dart'; import '../user.dart'; import '../session.dart'; @@ -143,7 +142,7 @@ class _RealmCore { }); } - ConfigHandle _createConfig(Configuration config) { + ConfigHandle _createConfig(Configuration config, SchedulerHandle schedulerHandle) { return using((Arena arena) { final schemaHandle = _createSchema(config.schema); final configPtr = _realmLib.realm_config_new(); @@ -151,7 +150,7 @@ class _RealmCore { _realmLib.realm_config_set_schema(configHandle._pointer, schemaHandle._pointer); _realmLib.realm_config_set_path(configHandle._pointer, config.path.toUtf8Ptr(arena)); - _realmLib.realm_config_set_scheduler(configHandle._pointer, scheduler.handle._pointer); + _realmLib.realm_config_set_scheduler(configHandle._pointer, schedulerHandle._pointer); if (config.fifoFilesFallbackPath != null) { _realmLib.realm_config_set_fifo_path(configHandle._pointer, config.fifoFilesFallbackPath!.toUtf8Ptr(arena)); @@ -287,7 +286,7 @@ class _RealmCore { completer.complete(SubscriptionSetState.values[state]); } - Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen) { + Future waitForSubscriptionSetStateChange(SubscriptionSet subscriptions, SubscriptionSetState notifyWhen, SchedulerHandle schedulerHandle) { final completer = Completer(); _realmLib.realm_dart_sync_on_subscription_set_state_change_async( subscriptions.handle._pointer, @@ -295,7 +294,7 @@ class _RealmCore { Pointer.fromFunction(stateChangeCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, - scheduler.handle._pointer, + schedulerHandle._pointer, ); return completer.future; } @@ -435,8 +434,8 @@ class _RealmCore { _realmLib.realm_scheduler_perform_work(schedulerHandle._pointer); } - RealmHandle openRealm(Configuration config) { - final configHandle = _createConfig(config); + RealmHandle openRealm(Configuration config, SchedulerHandle schedulerHandle) { + final configHandle = _createConfig(config, schedulerHandle); final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open(configHandle._pointer), "Error opening realm at path ${config.path}"); return RealmHandle._(realmPtr); } @@ -832,7 +831,7 @@ class _RealmCore { } } - RealmNotificationTokenHandle subscribeResultsNotifications(RealmResults results, NotificationsController controller) { + RealmNotificationTokenHandle subscribeResultsNotifications(RealmResults results, NotificationsController controller, SchedulerHandle schedulerHandle) { final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_results_add_notification_callback( results.handle._pointer, controller.toWeakHandle(), @@ -840,13 +839,13 @@ class _RealmCore { nullptr, Pointer.fromFunction(collection_change_callback), nullptr, - scheduler.handle._pointer, + schedulerHandle._pointer, )); return RealmNotificationTokenHandle._(pointer); } - RealmNotificationTokenHandle subscribeListNotifications(RealmList list, NotificationsController controller) { + RealmNotificationTokenHandle subscribeListNotifications(RealmList list, NotificationsController controller, SchedulerHandle schedulerHandle) { final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_list_add_notification_callback( list.handle._pointer, controller.toWeakHandle(), @@ -854,13 +853,13 @@ class _RealmCore { nullptr, Pointer.fromFunction(collection_change_callback), nullptr, - scheduler.handle._pointer, + schedulerHandle._pointer, )); return RealmNotificationTokenHandle._(pointer); } - RealmNotificationTokenHandle subscribeObjectNotifications(RealmObject object, NotificationsController controller) { + RealmNotificationTokenHandle subscribeObjectNotifications(RealmObject object, NotificationsController controller, SchedulerHandle schedulerHandle) { final pointer = _realmLib.invokeGetPointer(() => _realmLib.realm_object_add_notification_callback( object.handle._pointer, controller.toWeakHandle(), @@ -868,7 +867,7 @@ class _RealmCore { nullptr, Pointer.fromFunction(object_change_callback), nullptr, - scheduler.handle._pointer, + schedulerHandle._pointer, )); return RealmNotificationTokenHandle._(pointer); @@ -1071,7 +1070,7 @@ class _RealmCore { } } - SyncClientConfigHandle _createSyncClientConfig(AppConfiguration configuration) { + SyncClientConfigHandle _createSyncClientConfig(AppConfiguration configuration, SchedulerHandle schedulerHandle) { return using((arena) { final handle = SyncClientConfigHandle._(_realmLib.realm_sync_client_config_new()); @@ -1086,7 +1085,7 @@ class _RealmCore { Pointer.fromFunction(logCallback), nullptr, nullptr, - scheduler.handle._pointer, + schedulerHandle._pointer, ); } else { _realmLib.realm_sync_client_config_set_log_level(handle._pointer, RealmLogLevel.off.toInt()); @@ -1101,10 +1100,10 @@ class _RealmCore { }); } - AppHandle getApp(AppConfiguration configuration) { + AppHandle getApp(AppConfiguration configuration, SchedulerHandle schedulerHandle) { final httpTransportHandle = _createHttpTransport(configuration.httpClient); final appConfigHandle = _createAppConfig(configuration, httpTransportHandle); - final syncClientConfigHandle = _createSyncClientConfig(configuration); + final syncClientConfigHandle = _createSyncClientConfig(configuration, schedulerHandle); final realmAppPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_app_get(appConfigHandle._pointer, syncClientConfigHandle._pointer)); return AppHandle._(realmAppPtr); } @@ -1508,10 +1507,10 @@ class _RealmCore { _realmLib.realm_sync_session_resume(session.handle._pointer); } - int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller) { + int sessionRegisterProgressNotifier(Session session, ProgressDirection direction, ProgressMode mode, SessionProgressNotificationsController controller, SchedulerHandle schedulerHandle) { final isStreaming = mode == ProgressMode.reportIndefinitely; return _realmLib.realm_dart_sync_session_register_progress_notifier(session.handle._pointer, Pointer.fromFunction(on_sync_progress), direction.index, - isStreaming, controller.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer); + isStreaming, controller.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, schedulerHandle._pointer); } void sessionUnregisterProgressNotifier(Session session, int token) { @@ -1527,26 +1526,26 @@ class _RealmCore { controller.onProgress(transferred, transferable); } - Future sessionWaitForUpload(Session session) { + Future sessionWaitForUpload(Session session, SchedulerHandle schedulerHandle) { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_upload_completion( session.handle._pointer, Pointer.fromFunction(sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, - scheduler.handle._pointer, + schedulerHandle._pointer, ); return completer.future; } - Future sessionWaitForDownload(Session session) { + Future sessionWaitForDownload(Session session, SchedulerHandle schedulerHandle) { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_download_completion( session.handle._pointer, Pointer.fromFunction(sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, - scheduler.handle._pointer, + schedulerHandle._pointer, ); return completer.future; } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d0119d44d..66c8cecb4 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -94,7 +94,7 @@ class Realm { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? realmCore.openRealm(config) { + Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? realmCore.openRealm(config, scheduler.handle) { _populateMetadata(); } @@ -321,7 +321,10 @@ class Realm { ..level = RealmLogLevel.info ..onRecord.listen((event) => print(event)); - /// Shutdown. + /// Used to shutdown Realm and allow the process to correctly release native resources and exit. + /// + /// Disclaimer: This method is mostly needed on Dart standalone and if not called the Dart probram will hang and not exit. + /// This is a workaround of a Dart VM bug and will be removed in a future version of the SDK. static void shutdown() => scheduler.stop(); } diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index 6ffa90f99..577ad2752 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -18,6 +18,8 @@ import 'dart:async'; +import 'package:realm_dart/src/scheduler.dart'; + import 'list.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; @@ -343,7 +345,7 @@ class RealmObjectNotificationsController extends Notifica @override RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeObjectNotifications(realmObject, this); + return realmCore.subscribeObjectNotifications(realmObject, this, scheduler.handle); } Stream> createStream() { diff --git a/lib/src/results.dart b/lib/src/results.dart index 79d4b091d..4730b70c5 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -19,6 +19,8 @@ import 'dart:async'; import 'dart:collection' as collection; +import 'package:realm_dart/src/scheduler.dart'; + import 'collections.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; @@ -105,7 +107,7 @@ class ResultsNotificationsController extends Notification @override RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeResultsNotifications(results, this); + return realmCore.subscribeResultsNotifications(results, this, scheduler.handle); } Stream> createStream() { diff --git a/lib/src/session.dart b/lib/src/session.dart index 9e2c5f6fc..a67af49ff 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -18,6 +18,8 @@ import 'dart:async'; +import 'package:realm_dart/src/scheduler.dart'; + import '../realm.dart'; import 'native/realm_core.dart'; import 'user.dart'; @@ -57,10 +59,10 @@ class Session { void resume() => realmCore.sessionResume(this); /// Waits for the [Session] to finish all pending uploads. - Future waitForUpload() => realmCore.sessionWaitForUpload(this); + Future waitForUpload() => realmCore.sessionWaitForUpload(this, scheduler.handle); /// Waits for the [Session] to finish all pending downloads. - Future waitForDownload() => realmCore.sessionWaitForDownload(this); + Future waitForDownload() => realmCore.sessionWaitForDownload(this, scheduler.handle); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { @@ -166,7 +168,7 @@ class SessionProgressNotificationsController { throw RealmStateError("Session progress subscription already started"); } - _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this); + _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this, scheduler.handle); } void _stop() { diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 72a3674e3..5e56c7c31 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -19,6 +19,8 @@ import 'dart:core'; import 'dart:collection'; +import 'package:realm_dart/src/scheduler.dart'; + import 'native/realm_core.dart'; import 'realm_class.dart'; @@ -144,7 +146,7 @@ abstract class SubscriptionSet with IterableMixin { } Future _waitForStateChange(SubscriptionSetState state) async { - final result = await realmCore.waitForSubscriptionSetStateChange(this, state); + final result = await realmCore.waitForSubscriptionSetStateChange(this, state, scheduler.handle); realmCore.refreshSubscriptionSet(this); return result; } From b7782fa9ba28b7341049605ad25181f0cf4fe03c Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 17:27:51 +0300 Subject: [PATCH 107/122] update readme --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed7c3167..cd21352ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ * Support setting logger on Realm class. Default is to print info message or worse to the console. ([#583](https://github.com/realm/realm-dart/pull/583)) * Support SyncErrorHandler in FlexibleSyncConfiguration. ([#577](https://github.com/realm/realm-dart/pull/577)) * Support SyncClientResetHandler in FlexibleSyncConfiguration. ([#608](https://github.com/realm/realm-dart/pull/608)) +* Added Realm.Shutdown method to allow normal process exit in Dart applications. ([#617](https://github.com/realm/realm-dart/pull/617)) ### Fixed From 51ce90a1b89cb253b012fbd0953fd101bde1e7b2 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 17:30:15 +0300 Subject: [PATCH 108/122] update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd21352ed..de5944fcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,7 +44,7 @@ * Support setting logger on Realm class. Default is to print info message or worse to the console. ([#583](https://github.com/realm/realm-dart/pull/583)) * Support SyncErrorHandler in FlexibleSyncConfiguration. ([#577](https://github.com/realm/realm-dart/pull/577)) * Support SyncClientResetHandler in FlexibleSyncConfiguration. ([#608](https://github.com/realm/realm-dart/pull/608)) -* Added Realm.Shutdown method to allow normal process exit in Dart applications. ([#617](https://github.com/realm/realm-dart/pull/617)) +* [Dart] Added `Realm.Shutdown` method to allow normal process exit in Dart applications. ([#617](https://github.com/realm/realm-dart/pull/617)) ### Fixed From 1c0177bcc22bf484ae53fdc818b2252536417e07 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 17:43:27 +0300 Subject: [PATCH 109/122] fix imports --- lib/src/app.dart | 2 +- lib/src/list.dart | 3 +-- lib/src/realm_object.dart | 3 +-- lib/src/results.dart | 3 +-- lib/src/session.dart | 3 +-- lib/src/subscription.dart | 3 +-- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 1c0acde14..ce8f73100 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -19,13 +19,13 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; -import 'package:realm_dart/src/scheduler.dart'; import '../realm.dart'; import 'configuration.dart'; import 'credentials.dart'; import 'native/realm_core.dart'; import 'user.dart'; +import 'scheduler.dart'; /// A class exposing configuration options for an [App] /// {@category Application} diff --git a/lib/src/list.dart b/lib/src/list.dart index 0903cf5e2..a4c45dc93 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -19,13 +19,12 @@ import 'dart:async'; import 'dart:collection' as collection; -import 'package:realm_dart/src/scheduler.dart'; - import 'collections.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; import 'realm_object.dart'; import 'results.dart'; +import 'scheduler.dart'; /// Instances of this class are live collections and will update as new elements are either /// added to or deleted from the collection or from the Realm. diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index 577ad2752..104937c4c 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -18,11 +18,10 @@ import 'dart:async'; -import 'package:realm_dart/src/scheduler.dart'; - import 'list.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; +import 'scheduler.dart'; abstract class RealmAccessor { Object? get(RealmObject object, String name); diff --git a/lib/src/results.dart b/lib/src/results.dart index 4730b70c5..4c67b10a8 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -19,11 +19,10 @@ import 'dart:async'; import 'dart:collection' as collection; -import 'package:realm_dart/src/scheduler.dart'; - import 'collections.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; +import 'scheduler.dart'; /// Instances of this class are live collections and will update as new elements are either /// added to or deleted from the Realm that match the underlying query. diff --git a/lib/src/session.dart b/lib/src/session.dart index a67af49ff..da6c266ee 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -18,11 +18,10 @@ import 'dart:async'; -import 'package:realm_dart/src/scheduler.dart'; - import '../realm.dart'; import 'native/realm_core.dart'; import 'user.dart'; +import 'scheduler.dart'; /// An object encapsulating a synchronization session. Sessions represent the /// communication between the client (and a local Realm file on disk), and the diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 5e56c7c31..60c49e490 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -19,10 +19,9 @@ import 'dart:core'; import 'dart:collection'; -import 'package:realm_dart/src/scheduler.dart'; - import 'native/realm_core.dart'; import 'realm_class.dart'; +import 'scheduler.dart'; /// A class representing a single query subscription. The server will continuously /// evaluate the query that the app subscribed to and will send data From 47a441ad40934fcab9d7ddae2b8349a92c9a9c40 Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 21:00:00 +0300 Subject: [PATCH 110/122] use switch statement --- lib/src/native/realm_core.dart | 42 +++++++++++++++++----------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b30079aea..3408c8d9d 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -2046,27 +2046,27 @@ extension LevelExt on Level { } static Level fromInt(int value) { - if (value == 0) { - return RealmLogLevel.all; - } else if (value == 1) { - return RealmLogLevel.trace; - } else if (value == 2) { - return RealmLogLevel.debug; - } else if (value == 3) { - return RealmLogLevel.detail; - } else if (value == 4) { - return RealmLogLevel.info; - } else if (value == 5) { - return RealmLogLevel.warn; - } else if (value == 6) { - return RealmLogLevel.error; - } else if (value == 7) { - return RealmLogLevel.fatal; - } else if (value == 8) { - return RealmLogLevel.off; + switch (value) { + case 0: + return RealmLogLevel.all; + case 1: + return RealmLogLevel.trace; + case 2: + return RealmLogLevel.debug; + case 3: + return RealmLogLevel.detail; + case 4: + return RealmLogLevel.info; + case 5: + return RealmLogLevel.warn; + case 6: + return RealmLogLevel.error; + case 7: + return RealmLogLevel.fatal; + case 8: + default: + // if unknown logging is off + return RealmLogLevel.off; } - - // if unknown logging is off - return RealmLogLevel.off; } } From 405b49cc079b9052e77f961097af0ac67470e81f Mon Sep 17 00:00:00 2001 From: blagoev Date: Sun, 29 May 2022 21:06:17 +0300 Subject: [PATCH 111/122] bump flutter tests timeout --- flutter/realm_flutter/tests/test_driver/app_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/realm_flutter/tests/test_driver/app_test.dart b/flutter/realm_flutter/tests/test_driver/app_test.dart index 9d3464624..ef59d7200 100644 --- a/flutter/realm_flutter/tests/test_driver/app_test.dart +++ b/flutter/realm_flutter/tests/test_driver/app_test.dart @@ -28,11 +28,11 @@ void main(List args) { testCommandWithArgs += getArgFromEnvVariable("BAAS_PRIVATE_API_KEY"); testCommandWithArgs += getArgFromEnvVariable("BAAS_PROJECT_ID"); - String result = await driver!.requestData(testCommandWithArgs, timeout: const Duration(minutes: 30)); + String result = await driver!.requestData(testCommandWithArgs, timeout: const Duration(hours: 1)); if (result.isNotEmpty) { fail("Failed tests: \n $result"); } - }, timeout: const Timeout(Duration(minutes: 30))); + }, timeout: const Timeout(Duration(hours: 1))); }); } From 0eb1dc13295f148f36a6f79edb5706b8b6fb985c Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 30 May 2022 18:06:13 +0300 Subject: [PATCH 112/122] fix enums usage move SyncErrorHandler to a callback handle PR feedback --- common/lib/src/realm_types.dart | 61 ++++++++++++++++++++++++++++----- lib/src/configuration.dart | 25 ++++++-------- lib/src/native/realm_core.dart | 30 ++++++++-------- test/session_test.dart | 12 +++---- 4 files changed, 84 insertions(+), 44 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 79f8b238c..4aa34dd6d 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -124,15 +124,15 @@ class SyncError extends RealmError { static SyncError create(String message, SyncErrorCategory category, int code, {bool isFatal = false}) { switch (category) { case SyncErrorCategory.client: - final errorCode = SyncClientErrorCode.values[code]; + final SyncClientErrorCode errorCode = SyncClientErrorCode.fromInt(code); if (errorCode == SyncClientErrorCode.autoClientResetFailure) { return SyncClientResetError(message); } return SyncClientError(message, category, errorCode, isFatal: isFatal); case SyncErrorCategory.connection: - return SyncConnectionError(message, category, SyncConnectionErrorCode.values[code], isFatal: isFatal); + return SyncConnectionError(message, category, SyncConnectionErrorCode.fromInt(code), isFatal: isFatal); case SyncErrorCategory.session: - return SyncSessionError(message, category, SyncSessionErrorCode.values[code], isFatal: isFatal); + return SyncSessionError(message, category, SyncSessionErrorCode.fromInt(code), isFatal: isFatal); case SyncErrorCategory.system: case SyncErrorCategory.unknown: default: @@ -158,7 +158,7 @@ class SyncClientError extends SyncError { SyncErrorCategory category, SyncClientErrorCode errorCode, { this.isFatal = false, - }) : super(message, category, errorCode.index); + }) : super(message, category, errorCode.code); } /// An error type that describes a client reset error condition. @@ -170,7 +170,7 @@ class SyncClientResetError extends SyncError { /// The [ClientResetError] has error code of [SyncClientErrorCode.autoClientResetFailure] SyncClientErrorCode get code => SyncClientErrorCode.autoClientResetFailure; - SyncClientResetError(String message) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.index); + SyncClientResetError(String message) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.code); } /// An error type that describes a connection-level error condition. @@ -180,14 +180,14 @@ class SyncConnectionError extends SyncError { final bool isFatal; /// The [SyncConnectionErrorCode] value indicating the type of the sync error. - SyncConnectionErrorCode get code => SyncConnectionErrorCode.values[codeValue]; + SyncConnectionErrorCode get code => SyncConnectionErrorCode.fromInt(codeValue); SyncConnectionError( String message, SyncErrorCategory category, SyncConnectionErrorCode errorCode, { this.isFatal = false, - }) : super(message, category, errorCode.index); + }) : super(message, category, errorCode.code); } /// An error type that describes a session-level error condition. @@ -204,7 +204,7 @@ class SyncSessionError extends SyncError { SyncErrorCategory category, SyncSessionErrorCode errorCode, { this.isFatal = false, - }) : super(message, category, errorCode.index); + }) : super(message, category, errorCode.code); } /// A general or unknown sync error @@ -238,6 +238,17 @@ enum GeneralSyncErrorCode { // A general sync error code unknown(9999); + static final Map _valuesMap = {for (var value in GeneralSyncErrorCode.values) value.code: value}; + + static GeneralSyncErrorCode fromInt(int code) { + final mappedCode = GeneralSyncErrorCode._valuesMap[code]; + if (mappedCode == null) { + throw RealmError("Unknown GeneralSyncErrorCode"); + } + + return mappedCode; + } + final int code; const GeneralSyncErrorCode(this.code); } @@ -341,7 +352,19 @@ enum SyncClientErrorCode { /// A fatal error was encountered which prevents completion of a client reset autoClientResetFailure(132); + static final Map _valuesMap = {for (var value in SyncClientErrorCode.values) value.code: value}; + + static SyncClientErrorCode fromInt(int code) { + final mappedCode = SyncClientErrorCode._valuesMap[code]; + if (mappedCode == null) { + throw RealmError("Unknown SyncClientErrorCode"); + } + + return mappedCode; + } + final int code; + const SyncClientErrorCode(this.code); } @@ -395,6 +418,17 @@ enum SyncConnectionErrorCode { /// Connected with wrong wire protocol - should switch to PBS switchToPbs(114); + static final Map _valuesMap = {for (var value in SyncConnectionErrorCode.values) value.code: value}; + + static SyncConnectionErrorCode fromInt(int code) { + final mappedCode = SyncConnectionErrorCode._valuesMap[code]; + if (mappedCode == null) { + throw RealmError("Unknown SyncConnectionErrorCode"); + } + + return mappedCode; + } + final int code; const SyncConnectionErrorCode(this.code); } @@ -493,6 +527,17 @@ enum SyncSessionErrorCode { /// Client attempted a write that is disallowed by permissions, or modifies an object outside the current query - requires client reset (UPLOAD) writeNotAllowed(230); + static final Map _valuesMap = {for (var value in SyncSessionErrorCode.values) value.code: value}; + + static SyncSessionErrorCode fromInt(int code) { + final mappedCode = SyncSessionErrorCode._valuesMap[code]; + if (mappedCode == null) { + throw RealmError("Unknown SyncSessionErrorCode"); + } + + return mappedCode; + } + final int code; const SyncSessionErrorCode(this.code); } diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index e7c8f8006..f05834da6 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -59,7 +59,11 @@ abstract class Configuration { static String? _defaultPath; - Configuration._(List schemaObjects, {String? path, this.fifoFilesFallbackPath}) : schema = RealmSchema(schemaObjects) { + Configuration._( + List schemaObjects, { + String? path, + this.fifoFilesFallbackPath, + }) : schema = RealmSchema(schemaObjects) { this.path = _getPath(path); } @@ -208,6 +212,11 @@ enum SessionStopPolicy { afterChangesUploaded, // Once all Realms/Sessions go out of scope, wait for uploads to complete and stop. } +///The signature of a callback that will be invoked whenever a [SyncError] occurs for the synchronized Realm. +/// +/// Client reset errors will not be reported through this callback as they are handled by [SyncClientResetErrorHandler]. +typedef SyncErrorHandler = void Function(SyncError); + /// [FlexibleSyncConfiguration] is used to open [Realm] instances that are synchronized /// with MongoDB Realm. /// {@category Configuration} @@ -305,20 +314,6 @@ class RealmSchema extends Iterable { SchemaObject elementAt(int index) => _schema.elementAt(index); } -///The signature of a callback that will be invoked whenever a [SyncError] occurs for the synchronized Realm. -/// -/// Client reset errors will not be reported through this callback as they are handled by [ClientResetHandler]. -class SyncErrorHandler { - /// The callback that handles the [SyncError]. - void Function(SyncError code) callback; - - /// Invokes the [SyncError] handling [callback]. - void call(SyncError error) => callback(error); - - /// Initializes a new instance of of [SyncErrorHandler]. - SyncErrorHandler(this.callback); -} - /// The signature of a callback that will be invoked if a client reset error occurs for this [Realm]. /// /// Currently, Flexible sync only supports the Manual Recovery. diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index e910299c5..17c6af6dc 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -190,7 +190,7 @@ class _RealmCore { 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_error_handler(syncConfigPtr, Pointer.fromFunction(syncErrorHandlerCallback), config.toPersistentHandle(), + _realmLib.realm_sync_config_set_error_handler(syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle); _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { @@ -279,7 +279,7 @@ class _RealmCore { return result == nullptr ? null : SubscriptionHandle._(result); } - static void stateChangeCallback(Pointer userdata, int state) { + static void _stateChangeCallback(Pointer userdata, int state) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; @@ -292,7 +292,7 @@ class _RealmCore { _realmLib.realm_dart_sync_on_subscription_set_state_change_async( subscriptions.handle._pointer, notifyWhen.index, - Pointer.fromFunction(stateChangeCallback), + Pointer.fromFunction(_stateChangeCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -405,7 +405,7 @@ class _RealmCore { return config.shouldCompactCallback!(totalSize, usedSize) ? TRUE : FALSE; } - static void syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { + static void _syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { final FlexibleSyncConfiguration? syncConfig = userdata.toObject(isPersistent: true); if (syncConfig == null) { return; @@ -1053,7 +1053,7 @@ class _RealmCore { }); } - static void logCallback(Pointer userdata, int levelAsInt, Pointer message) { + static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { try { final logger = Realm.logger; final level = _LogLevel.values[levelAsInt].loggerLevel; @@ -1077,7 +1077,7 @@ class _RealmCore { _realmLib.realm_sync_client_config_set_log_level(handle._pointer, _LogLevel.fromLevel(Realm.logger.level).index); _realmLib.realm_dart_sync_client_config_set_log_callback( handle._pointer, - Pointer.fromFunction(logCallback), + Pointer.fromFunction(_logCallback), nullptr, nullptr, scheduler.handle._pointer, @@ -1104,7 +1104,7 @@ class _RealmCore { return _realmLib.realm_app_get_app_id(app.handle._pointer).cast().toRealmDartString()!; } - static void app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { + static void _app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { final Completer? completer = userdata.toObject(isPersistent: true); if (completer == null) { return; @@ -1131,7 +1131,7 @@ class _RealmCore { () => _realmLib.realm_app_log_in_with_credentials( app.handle._pointer, credentials.handle._pointer, - Pointer.fromFunction(app_user_completion_callback), + Pointer.fromFunction(_app_user_completion_callback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1266,7 +1266,7 @@ class _RealmCore { return UserHandle._(userPtr); } - static void logOutCallback(Pointer userdata, Pointer error) { + static void _logOutCallback(Pointer userdata, Pointer error) { final Completer? completer = userdata.toObject(isPersistent: true); if (completer == null) { return; @@ -1287,7 +1287,7 @@ class _RealmCore { _realmLib.invokeGetBool( () => _realmLib.realm_app_log_out_current_user( application.handle._pointer, - Pointer.fromFunction(logOutCallback), + Pointer.fromFunction(_logOutCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1297,7 +1297,7 @@ class _RealmCore { () => _realmLib.realm_app_log_out( application.handle._pointer, user.handle._pointer, - Pointer.fromFunction(logOutCallback), + Pointer.fromFunction(_logOutCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1385,7 +1385,7 @@ class _RealmCore { app.handle._pointer, user.handle._pointer, credentials.handle._pointer, - Pointer.fromFunction(app_user_completion_callback), + Pointer.fromFunction(_app_user_completion_callback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1522,7 +1522,7 @@ class _RealmCore { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_upload_completion( session.handle._pointer, - Pointer.fromFunction(sessionWaitCompletionCallback), + Pointer.fromFunction(_sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -1534,7 +1534,7 @@ class _RealmCore { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_download_completion( session.handle._pointer, - Pointer.fromFunction(sessionWaitCompletionCallback), + Pointer.fromFunction(_sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -1542,7 +1542,7 @@ class _RealmCore { return completer.future; } - static void sessionWaitCompletionCallback(Pointer userdata, Pointer errorCode) { + static void _sessionWaitCompletionCallback(Pointer userdata, Pointer errorCode) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; diff --git a/test/session_test.dart b/test/session_test.dart index 29d50603b..7720b1366 100644 --- a/test/session_test.dart +++ b/test/session_test.dart @@ -279,14 +279,14 @@ Future main([List? args]) async { baasTest('SyncSession test error handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: SyncErrorHandler((syncError) { + final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: (syncError) { expect(syncError, isA()); final sessionError = syncError.as(); expect(sessionError.category, SyncErrorCategory.session); expect(sessionError.isFatal, false); expect(sessionError.code, SyncSessionErrorCode.badAuthentication); expect(sessionError.message, "Simulated session error"); - })); + }); final realm = getRealm(config); @@ -296,14 +296,14 @@ Future main([List? args]) async { baasTest('SyncSession test fatal error handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: SyncErrorHandler((syncError) { + final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: (syncError) { expect(syncError, isA()); final syncClientError = syncError.as(); expect(syncClientError.category, SyncErrorCategory.client); expect(syncClientError.isFatal, true); expect(syncClientError.code, SyncClientErrorCode.badChangeset); expect(syncClientError.message, "Simulated client error"); - })); + }); final realm = getRealm(config); realm.syncSession.raiseError(SyncErrorCategory.client, SyncClientErrorCode.badChangeset.index, true); @@ -312,14 +312,14 @@ Future main([List? args]) async { baasTest('SyncSession client reset handler', (configuration) async { final app = App(configuration); final user = await getIntegrationUser(app); - final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: SyncErrorHandler((syncError) { + final config = Configuration.flexibleSync(user, [Task.schema], syncErrorHandler: (syncError) { expect(syncError, isA()); final syncClientResetError = syncError.as(); expect(syncClientResetError.category, SyncErrorCategory.session); expect(syncClientResetError.isFatal, true); expect(syncClientResetError.code, SyncClientErrorCode.autoClientResetFailure); expect(syncClientResetError.message, "Simulated session error"); - })); + }); final realm = getRealm(config); realm.syncSession.raiseError(SyncErrorCategory.session, SyncClientErrorCode.autoClientResetFailure.index, true); From e07954b72ed99ade13900b0da7b44c1a8edfb142 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 30 May 2022 18:20:08 +0300 Subject: [PATCH 113/122] enable logger with info level handle PR feedback --- lib/src/native/realm_core.dart | 49 +++++++++++++++------------------- lib/src/realm_class.dart | 9 ++----- 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 3408c8d9d..e42c33867 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -986,22 +986,22 @@ class _RealmCore { late HttpClientRequest request; // this throws if requestMethod is unknown _HttpMethod - final method = HttpMethod.values[requestMethod]; + final method = _HttpMethod.values[requestMethod]; switch (method) { - case HttpMethod.delete: + case _HttpMethod.delete: request = await client.deleteUrl(url); break; - case HttpMethod.put: + case _HttpMethod.put: request = await client.putUrl(url); break; - case HttpMethod.patch: + case _HttpMethod.patch: request = await client.patchUrl(url); break; - case HttpMethod.post: + case _HttpMethod.post: request = await client.postUrl(url); break; - case HttpMethod.get: + case _HttpMethod.get: request = await client.getUrl(url); break; } @@ -1039,14 +1039,14 @@ class _RealmCore { } }); - responseRef.custom_status_code = CustomErrorCode.noError.code; + responseRef.custom_status_code = _CustomErrorCode.noError.code; } on SocketException catch (_) { // TODO: A Timeout causes a socket exception, but not all socket exceptions are due to timeouts - responseRef.custom_status_code = CustomErrorCode.timeout.code; + responseRef.custom_status_code = _CustomErrorCode.timeout.code; } on HttpException catch (_) { - responseRef.custom_status_code = CustomErrorCode.unknownHttp.code; + responseRef.custom_status_code = _CustomErrorCode.unknownHttp.code; } catch (_) { - responseRef.custom_status_code = CustomErrorCode.unknown.code; + responseRef.custom_status_code = _CustomErrorCode.unknown.code; } finally { _realmLib.realm_http_transport_complete_request(request_context, response_pointer); } @@ -1055,9 +1055,6 @@ class _RealmCore { static void logCallback(Pointer userdata, int levelAsInt, Pointer message) { final logger = Realm.logger; - if (logger == null) { - return; - } try { final level = LevelExt.fromInt(levelAsInt); @@ -1078,19 +1075,15 @@ class _RealmCore { _realmLib.realm_sync_client_config_set_base_file_path(handle._pointer, configuration.baseFilePath.path.toUtf8Ptr(arena)); _realmLib.realm_sync_client_config_set_metadata_mode(handle._pointer, configuration.metadataPersistenceMode.index); - if (Realm.logger != null) { - _realmLib.realm_sync_client_config_set_log_level(handle._pointer, Realm.logger!.level.toInt()); + _realmLib.realm_sync_client_config_set_log_level(handle._pointer, Realm.logger.level.toInt()); - _realmLib.realm_dart_sync_client_config_set_log_callback( - handle._pointer, - Pointer.fromFunction(logCallback), - nullptr, - nullptr, - scheduler.handle._pointer, - ); - } else { - _realmLib.realm_sync_client_config_set_log_level(handle._pointer, RealmLogLevel.off.toInt()); - } + _realmLib.realm_dart_sync_client_config_set_log_callback( + handle._pointer, + Pointer.fromFunction(logCallback), + nullptr, + nullptr, + scheduler.handle._pointer, + ); _realmLib.realm_sync_client_config_set_connect_timeout(handle._pointer, configuration.maxConnectionTimeout.inMicroseconds); if (configuration.metadataEncryptionKey != null && configuration.metadataPersistenceMode == MetadataPersistenceMode.encrypted) { @@ -1969,7 +1962,7 @@ extension on List { } } -enum CustomErrorCode { +enum _CustomErrorCode { noError(0), // ignore: unused_field httpClientDisposed(997), @@ -1978,10 +1971,10 @@ enum CustomErrorCode { timeout(1000); final int code; - const CustomErrorCode(this.code); + const _CustomErrorCode(this.code); } -enum HttpMethod { +enum _HttpMethod { get, post, patch, diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d0119d44d..ad6f3701f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -311,13 +311,8 @@ class Realm { return realmCore.realmEquals(this, other); } - /// The logger to use for logging or null. - /// - /// To enable logging assign a logger instance and recreate any [FlexibleSyncConfiguration] configuraitons and reopen the synced [Realm]s. - static Logger? logger; - - /// This default logger logs to the console with [RealmLogLevel.info] level. - static Logger get defaultLogger => Logger.detached('Realm') + /// The logger to use for logging + static Logger logger = Logger.detached('Realm') ..level = RealmLogLevel.info ..onRecord.listen((event) => print(event)); From 3c8cbd4025bbb99273ce08867b329dd69555cf68 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 30 May 2022 18:34:44 +0300 Subject: [PATCH 114/122] fix bad merge --- lib/src/configuration.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 95afb35de..b37e8a3f3 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -232,7 +232,7 @@ class FlexibleSyncConfiguration extends Configuration { super.schemaObjects, { super.fifoFilesFallbackPath, super.path, - this.sessionErrorHandler, + this.syncErrorHandler, this.syncClientResetErrorHandler, }) : super._(); From bc5ae9bebbb97f7c30628e9eeda44df144cf3401 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 30 May 2022 18:37:42 +0300 Subject: [PATCH 115/122] fix usage --- test/app_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/app_test.dart b/test/app_test.dart index fe132ef74..87ad58b37 100644 --- a/test/app_test.dart +++ b/test/app_test.dart @@ -195,7 +195,7 @@ Future main([List? args]) async { await testLogger( configuration, - Realm.logger!, + Realm.logger, maxExpectedCounts: { // No problems expected! RealmLogLevel.fatal: 0, From b89e89988762aeb6abb18e262a0ae41a6d171de7 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 30 May 2022 20:03:16 +0300 Subject: [PATCH 116/122] fix bad merge --- lib/src/native/realm_core.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c7f22afc4..c9a3f5fb6 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1053,7 +1053,7 @@ class _RealmCore { }); } - static void logCallback(Pointer userdata, int levelAsInt, Pointer message) { + static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { final logger = Realm.logger; try { From 333c2fc402de3357c0764d3d7f82896c9e188a27 Mon Sep 17 00:00:00 2001 From: blagoev Date: Mon, 30 May 2022 20:29:26 +0300 Subject: [PATCH 117/122] fix bad merge --- lib/src/app.dart | 2 +- lib/src/list.dart | 2 +- lib/src/realm_class.dart | 2 +- lib/src/realm_object.dart | 2 +- lib/src/results.dart | 2 +- lib/src/session.dart | 6 +++--- lib/src/subscription.dart | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index ce8f73100..88343ca16 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -118,7 +118,7 @@ class App { String get id => realmCore.appGetId(this); /// Create an app with a particular [AppConfiguration] - App(AppConfiguration configuration) : this._(realmCore.getApp(configuration, scheduler.handle)); + App(AppConfiguration configuration) : this._(realmCore.getApp(configuration)); App._(this._handle); diff --git a/lib/src/list.dart b/lib/src/list.dart index 592eae03d..f1fac953a 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -191,7 +191,7 @@ class ListNotificationsController extends NotificationsControl @override RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeListNotifications(list, this, scheduler.handle); + return realmCore.subscribeListNotifications(list, this); } Stream> createStream() { diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5ba4d2360..38ec14641 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -94,7 +94,7 @@ class Realm { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? realmCore.openRealm(config, scheduler.handle) { + Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? realmCore.openRealm(config) { _populateMetadata(); } diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index 104937c4c..f6fcb8c02 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -344,7 +344,7 @@ class RealmObjectNotificationsController extends Notifica @override RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeObjectNotifications(realmObject, this, scheduler.handle); + return realmCore.subscribeObjectNotifications(realmObject, this); } Stream> createStream() { diff --git a/lib/src/results.dart b/lib/src/results.dart index 6612059db..e20789bee 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -106,7 +106,7 @@ class ResultsNotificationsController extends Notification @override RealmNotificationTokenHandle subscribe() { - return realmCore.subscribeResultsNotifications(results, this, scheduler.handle); + return realmCore.subscribeResultsNotifications(results, this); } Stream> createStream() { diff --git a/lib/src/session.dart b/lib/src/session.dart index da6c266ee..91ea9a97e 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -58,10 +58,10 @@ class Session { void resume() => realmCore.sessionResume(this); /// Waits for the [Session] to finish all pending uploads. - Future waitForUpload() => realmCore.sessionWaitForUpload(this, scheduler.handle); + Future waitForUpload() => realmCore.sessionWaitForUpload(this); /// Waits for the [Session] to finish all pending downloads. - Future waitForDownload() => realmCore.sessionWaitForDownload(this, scheduler.handle); + Future waitForDownload() => realmCore.sessionWaitForDownload(this); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { @@ -167,7 +167,7 @@ class SessionProgressNotificationsController { throw RealmStateError("Session progress subscription already started"); } - _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this, scheduler.handle); + _token = realmCore.sessionRegisterProgressNotifier(_session, _direction, _mode, this); } void _stop() { diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 8af99052e..251137569 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -145,7 +145,7 @@ abstract class SubscriptionSet with IterableMixin { } Future _waitForStateChange(SubscriptionSetState state) async { - final result = await realmCore.waitForSubscriptionSetStateChange(this, state, scheduler.handle); + final result = await realmCore.waitForSubscriptionSetStateChange(this, state); realmCore.refreshSubscriptionSet(this); return result; } From 8abf2cbe65df8b3c5127a91fb95200f2c38ed3ba Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 May 2022 00:27:03 +0300 Subject: [PATCH 118/122] use private methods for all callbacks --- lib/src/native/realm_core.dart | 52 +++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b2f17b4a4..f696288d4 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -190,7 +190,7 @@ class _RealmCore { 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_error_handler(syncConfigPtr, Pointer.fromFunction(syncErrorHandlerCallback), config.toPersistentHandle(), + _realmLib.realm_sync_config_set_error_handler(syncConfigPtr, Pointer.fromFunction(_syncErrorHandlerCallback), config.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle); _realmLib.realm_config_set_sync_config(configPtr, syncConfigPtr); } finally { @@ -279,7 +279,7 @@ class _RealmCore { return result == nullptr ? null : SubscriptionHandle._(result); } - static void stateChangeCallback(Pointer userdata, int state) { + static void _stateChangeCallback(Pointer userdata, int state) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; @@ -292,7 +292,7 @@ class _RealmCore { _realmLib.realm_dart_sync_on_subscription_set_state_change_async( subscriptions.handle._pointer, notifyWhen.index, - Pointer.fromFunction(stateChangeCallback), + Pointer.fromFunction(_stateChangeCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -405,7 +405,7 @@ class _RealmCore { return config.shouldCompactCallback!(totalSize, usedSize) ? TRUE : FALSE; } - static void syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { + static void _syncErrorHandlerCallback(Pointer userdata, Pointer user, realm_sync_error error) { final FlexibleSyncConfiguration? syncConfig = userdata.toObject(isPersistent: true); if (syncConfig == null) { return; @@ -986,22 +986,22 @@ class _RealmCore { late HttpClientRequest request; // this throws if requestMethod is unknown _HttpMethod - final method = HttpMethod.values[requestMethod]; + final method = _HttpMethod.values[requestMethod]; switch (method) { - case HttpMethod.delete: + case _HttpMethod.delete: request = await client.deleteUrl(url); break; - case HttpMethod.put: + case _HttpMethod.put: request = await client.putUrl(url); break; - case HttpMethod.patch: + case _HttpMethod.patch: request = await client.patchUrl(url); break; - case HttpMethod.post: + case _HttpMethod.post: request = await client.postUrl(url); break; - case HttpMethod.get: + case _HttpMethod.get: request = await client.getUrl(url); break; } @@ -1039,14 +1039,14 @@ class _RealmCore { } }); - responseRef.custom_status_code = CustomErrorCode.noError.code; + responseRef.custom_status_code = _CustomErrorCode.noError.code; } on SocketException catch (_) { // TODO: A Timeout causes a socket exception, but not all socket exceptions are due to timeouts - responseRef.custom_status_code = CustomErrorCode.timeout.code; + responseRef.custom_status_code = _CustomErrorCode.timeout.code; } on HttpException catch (_) { - responseRef.custom_status_code = CustomErrorCode.unknownHttp.code; + responseRef.custom_status_code = _CustomErrorCode.unknownHttp.code; } catch (_) { - responseRef.custom_status_code = CustomErrorCode.unknown.code; + responseRef.custom_status_code = _CustomErrorCode.unknown.code; } finally { _realmLib.realm_http_transport_complete_request(request_context, response_pointer); } @@ -1109,7 +1109,7 @@ class _RealmCore { return _realmLib.realm_app_get_app_id(app.handle._pointer).cast().toRealmDartString()!; } - static void app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { + static void _app_user_completion_callback(Pointer userdata, Pointer user, Pointer error) { final Completer? completer = userdata.toObject(isPersistent: true); if (completer == null) { return; @@ -1136,7 +1136,7 @@ class _RealmCore { () => _realmLib.realm_app_log_in_with_credentials( app.handle._pointer, credentials.handle._pointer, - Pointer.fromFunction(app_user_completion_callback), + Pointer.fromFunction(_app_user_completion_callback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1271,7 +1271,7 @@ class _RealmCore { return UserHandle._(userPtr); } - static void logOutCallback(Pointer userdata, Pointer error) { + static void _logOutCallback(Pointer userdata, Pointer error) { final Completer? completer = userdata.toObject(isPersistent: true); if (completer == null) { return; @@ -1292,7 +1292,7 @@ class _RealmCore { _realmLib.invokeGetBool( () => _realmLib.realm_app_log_out_current_user( application.handle._pointer, - Pointer.fromFunction(logOutCallback), + Pointer.fromFunction(_logOutCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1302,7 +1302,7 @@ class _RealmCore { () => _realmLib.realm_app_log_out( application.handle._pointer, user.handle._pointer, - Pointer.fromFunction(logOutCallback), + Pointer.fromFunction(_logOutCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1390,7 +1390,7 @@ class _RealmCore { app.handle._pointer, user.handle._pointer, credentials.handle._pointer, - Pointer.fromFunction(app_user_completion_callback), + Pointer.fromFunction(_app_user_completion_callback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, ), @@ -1527,7 +1527,7 @@ class _RealmCore { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_upload_completion( session.handle._pointer, - Pointer.fromFunction(sessionWaitCompletionCallback), + Pointer.fromFunction(_sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -1539,7 +1539,7 @@ class _RealmCore { final completer = Completer(); _realmLib.realm_dart_sync_session_wait_for_download_completion( session.handle._pointer, - Pointer.fromFunction(sessionWaitCompletionCallback), + Pointer.fromFunction(_sessionWaitCompletionCallback), completer.toPersistentHandle(), _realmLib.addresses.realm_dart_delete_persistent_handle, scheduler.handle._pointer, @@ -1547,7 +1547,7 @@ class _RealmCore { return completer.future; } - static void sessionWaitCompletionCallback(Pointer userdata, Pointer errorCode) { + static void _sessionWaitCompletionCallback(Pointer userdata, Pointer errorCode) { final completer = userdata.toObject>(isPersistent: true); if (completer == null) { return; @@ -1965,7 +1965,7 @@ extension on List { } } -enum CustomErrorCode { +enum _CustomErrorCode { noError(0), // ignore: unused_field httpClientDisposed(997), @@ -1974,10 +1974,10 @@ enum CustomErrorCode { timeout(1000); final int code; - const CustomErrorCode(this.code); + const _CustomErrorCode(this.code); } -enum HttpMethod { +enum _HttpMethod { get, post, patch, From e5f3d8220f0c2a55797195b4e7fd9cbffed675a7 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 May 2022 00:28:58 +0300 Subject: [PATCH 119/122] forgoten change --- lib/src/native/realm_core.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index f696288d4..c9a3f5fb6 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1055,9 +1055,6 @@ class _RealmCore { static void _logCallback(Pointer userdata, int levelAsInt, Pointer message) { final logger = Realm.logger; - if (logger == null) { - return; - } try { final level = LevelExt.fromInt(levelAsInt); From 18b7456ad7d6e2eadca1f95f4fcfc251d7b0573e Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 May 2022 10:20:19 +0300 Subject: [PATCH 120/122] add ManualSyncClientResetHandler add default SyncClientResetHandler and SyncClientErrorHandlers --- common/lib/src/realm_types.dart | 30 ++++++++++++++++++++++++++ lib/src/configuration.dart | 37 +++++++++++++++++++++++---------- lib/src/native/realm_core.dart | 12 +++++------ 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/common/lib/src/realm_types.dart b/common/lib/src/realm_types.dart index 0cf01b0eb..44000c0f9 100644 --- a/common/lib/src/realm_types.dart +++ b/common/lib/src/realm_types.dart @@ -142,6 +142,11 @@ class SyncError extends RealmError { /// As a specific [SyncError] type. T as() => this as T; + + @override + String toString() { + return "SyncError message: $message category: $category code: $codeValue"; + } } /// An error type that describes a session-level error condition. @@ -159,6 +164,11 @@ class SyncClientError extends SyncError { SyncClientErrorCode errorCode, { this.isFatal = false, }) : super(message, category, errorCode.code); + + @override + String toString() { + return "SyncError message: $message category: $category code: $code isFatal: $isFatal"; + } } /// An error type that describes a client reset error condition. @@ -171,6 +181,11 @@ class SyncClientResetError extends SyncError { SyncClientErrorCode get code => SyncClientErrorCode.autoClientResetFailure; SyncClientResetError(String message) : super(message, SyncErrorCategory.client, SyncClientErrorCode.autoClientResetFailure.code); + + @override + String toString() { + return "SyncError message: $message category: $category code: $code isFatal: $isFatal"; + } } /// An error type that describes a connection-level error condition. @@ -188,6 +203,11 @@ class SyncConnectionError extends SyncError { SyncConnectionErrorCode errorCode, { this.isFatal = false, }) : super(message, category, errorCode.code); + + @override + String toString() { + return "SyncError message: $message category: $category code: $code isFatal: $isFatal"; + } } /// An error type that describes a session-level error condition. @@ -205,6 +225,11 @@ class SyncSessionError extends SyncError { SyncSessionErrorCode errorCode, { this.isFatal = false, }) : super(message, category, errorCode.code); + + @override + String toString() { + return "SyncError message: $message category: $category code: $code isFatal: $isFatal"; + } } /// A general or unknown sync error @@ -213,6 +238,11 @@ class GeneralSyncError extends SyncError { int get code => codeValue; GeneralSyncError(String message, SyncErrorCategory category, int code) : super(message, category, code); + + @override + String toString() { + return "SyncError message: $message category: $category code: $code"; + } } /// The category of a [SyncError]. diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index b37e8a3f3..ed94bc0c1 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -136,8 +136,8 @@ abstract class Configuration { List schemaObjects, { String? fifoFilesFallbackPath, String? path, - SyncErrorHandler? syncErrorHandler, - SyncClientResetErrorHandler? syncClientResetErrorHandler, + SyncErrorHandler syncErrorHandler = defaultSyncErrorHandler, + SyncClientResetErrorHandler syncClientResetErrorHandler = const ManualSyncClientResetHandler(_defaultSyncClientResetHandler), }) => FlexibleSyncConfiguration._( user, @@ -213,6 +213,18 @@ enum SessionStopPolicy { /// Client reset errors will not be reported through this callback as they are handled by [SyncClientResetErrorHandler]. typedef SyncErrorHandler = void Function(SyncError); +void defaultSyncErrorHandler(SyncError e) { + Realm.logger.log(RealmLogLevel.error, e); +} + +void _defaultSyncClientResetHandler(SyncError e) { + Realm.logger.log(RealmLogLevel.error, "A client reset error occurred but no handler was supplied. " + "Synchronization is now paused and will resume automatically once the app is restarted and " + "the server data is redownloaded. Any unsynchronized changes the client has made or will " + "make will be lost. To handle that scenario, pass in a non-null value to" + "syncClientResetErrorHandler when constructing Configuration.flexibleSync."); +} + /// [FlexibleSyncConfiguration] is used to open [Realm] instances that are synchronized /// with MongoDB Realm. /// {@category Configuration} @@ -222,18 +234,22 @@ class FlexibleSyncConfiguration extends Configuration { SessionStopPolicy _sessionStopPolicy = SessionStopPolicy.afterChangesUploaded; /// Called when a [SyncError] occurs for this synchronized [Realm]. - final SyncErrorHandler? syncErrorHandler; + /// + /// The default [SyncErrorHandler] prints to the console + final SyncErrorHandler syncErrorHandler; /// Called when a [SyncClientResetError] occurs for this synchronized [Realm] - final SyncClientResetErrorHandler? syncClientResetErrorHandler; + /// + /// The default [SyncClientResetErrorHandler] logs a message using the current Realm.logger + final SyncClientResetErrorHandler syncClientResetErrorHandler; FlexibleSyncConfiguration._( this.user, super.schemaObjects, { super.fifoFilesFallbackPath, super.path, - this.syncErrorHandler, - this.syncClientResetErrorHandler, + this.syncErrorHandler = defaultSyncErrorHandler, + this.syncClientResetErrorHandler = const ManualSyncClientResetHandler(_defaultSyncClientResetHandler), }) : super._(); @override @@ -307,11 +323,10 @@ class RealmSchema extends Iterable { /// Currently, Flexible sync only supports the Manual Recovery. class SyncClientResetErrorHandler { /// The callback that handles the [SyncClientResetError]. - void Function(SyncClientResetError code) callback; - - /// Invokes the [SyncClientResetError] handling [callback]. - void call(SyncClientResetError error) => callback(error); + final void Function(SyncClientResetError code) callback; /// Initializes a new instance of of [SyncClientResetErrorHandler]. - SyncClientResetErrorHandler(this.callback); + const SyncClientResetErrorHandler(this.callback); } + +typedef ManualSyncClientResetHandler = SyncClientResetErrorHandler; diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 17c6af6dc..401355cd5 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -414,12 +414,11 @@ class _RealmCore { final syncError = error.toSyncError(); if (syncError is SyncClientResetError) { - if (syncConfig.syncClientResetErrorHandler != null) { - syncConfig.syncClientResetErrorHandler!(syncError); - } - } else if (syncConfig.syncErrorHandler != null) { - syncConfig.syncErrorHandler!(syncError); - } + syncConfig.syncClientResetErrorHandler!.callback(syncError); + return; + } + + syncConfig.syncErrorHandler(syncError); } void raiseError(Session session, SyncErrorCategory category, int errorCode, bool isFatal) { @@ -1962,7 +1961,6 @@ extension on List { enum _CustomErrorCode { noError(0), - httpClientDisposed(997), unknownHttp(998), unknown(999), timeout(1000); From 3e35b30d29332c6860c921335cf8b9931267f097 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 May 2022 12:29:15 +0300 Subject: [PATCH 121/122] remove unused imports --- lib/src/app.dart | 2 -- lib/src/collections.dart | 1 - lib/src/list.dart | 1 - lib/src/realm_object.dart | 1 - lib/src/results.dart | 1 - lib/src/session.dart | 1 - lib/src/subscription.dart | 1 - 7 files changed, 8 deletions(-) diff --git a/lib/src/app.dart b/lib/src/app.dart index 88343ca16..c03db166f 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -17,7 +17,6 @@ //////////////////////////////////////////////////////////////////////////////// import 'dart:io'; -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import '../realm.dart'; @@ -25,7 +24,6 @@ import 'configuration.dart'; import 'credentials.dart'; import 'native/realm_core.dart'; import 'user.dart'; -import 'scheduler.dart'; /// A class exposing configuration options for an [App] /// {@category Application} diff --git a/lib/src/collections.dart b/lib/src/collections.dart index 20bfd698b..f62a30d10 100644 --- a/lib/src/collections.dart +++ b/lib/src/collections.dart @@ -1,4 +1,3 @@ -import 'realm_class.dart'; import 'native/realm_core.dart'; /// Contains index information about objects that moved within the same collection. diff --git a/lib/src/list.dart b/lib/src/list.dart index f1fac953a..a897a7055 100644 --- a/lib/src/list.dart +++ b/lib/src/list.dart @@ -24,7 +24,6 @@ import 'native/realm_core.dart'; import 'realm_class.dart'; import 'realm_object.dart'; import 'results.dart'; -import 'scheduler.dart'; /// Instances of this class are live collections and will update as new elements are either /// added to or deleted from the collection or from the Realm. diff --git a/lib/src/realm_object.dart b/lib/src/realm_object.dart index f6fcb8c02..6ffa90f99 100644 --- a/lib/src/realm_object.dart +++ b/lib/src/realm_object.dart @@ -21,7 +21,6 @@ import 'dart:async'; import 'list.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; -import 'scheduler.dart'; abstract class RealmAccessor { Object? get(RealmObject object, String name); diff --git a/lib/src/results.dart b/lib/src/results.dart index e20789bee..6a78c4e7f 100644 --- a/lib/src/results.dart +++ b/lib/src/results.dart @@ -22,7 +22,6 @@ import 'dart:collection' as collection; import 'collections.dart'; import 'native/realm_core.dart'; import 'realm_class.dart'; -import 'scheduler.dart'; /// Instances of this class are live collections and will update as new elements are either /// added to or deleted from the Realm that match the underlying query. diff --git a/lib/src/session.dart b/lib/src/session.dart index 91ea9a97e..9e2c5f6fc 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -21,7 +21,6 @@ import 'dart:async'; import '../realm.dart'; import 'native/realm_core.dart'; import 'user.dart'; -import 'scheduler.dart'; /// An object encapsulating a synchronization session. Sessions represent the /// communication between the client (and a local Realm file on disk), and the diff --git a/lib/src/subscription.dart b/lib/src/subscription.dart index 251137569..04d04e8e0 100644 --- a/lib/src/subscription.dart +++ b/lib/src/subscription.dart @@ -21,7 +21,6 @@ import 'dart:collection'; import 'native/realm_core.dart'; import 'realm_class.dart'; -import 'scheduler.dart'; /// A class representing a single query subscription. The server will continuously /// evaluate the query that the app subscribed to and will send data From 92a717a5e620466005f16d0ceef1ba0cbe0b3ef8 Mon Sep 17 00:00:00 2001 From: blagoev Date: Tue, 31 May 2022 13:49:45 +0300 Subject: [PATCH 122/122] fix usage --- lib/src/native/realm_core.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 401355cd5..e105072a1 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -414,7 +414,7 @@ class _RealmCore { final syncError = error.toSyncError(); if (syncError is SyncClientResetError) { - syncConfig.syncClientResetErrorHandler!.callback(syncError); + syncConfig.syncClientResetErrorHandler.callback(syncError); return; }