From 3e23f73a3d94e961f42299c6e124eee0b0709406 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sat, 30 Jul 2022 14:40:28 +0300 Subject: [PATCH 001/113] Implement openRealmAsync in realm_core --- ffigen/README.md | 2 +- lib/src/native/realm_bindings.dart | 34 ++++++++++++++++++++++++++++++ lib/src/native/realm_core.dart | 22 +++++++++++++++++++ src/realm_dart_sync.cpp | 23 ++++++++++++++++++++ src/realm_dart_sync.h | 2 ++ 5 files changed, 82 insertions(+), 1 deletion(-) diff --git a/ffigen/README.md b/ffigen/README.md index 4577156a4..8ea352952 100644 --- a/ffigen/README.md +++ b/ffigen/README.md @@ -3,7 +3,7 @@ The is executed manually as needed and this package is never published. Usage: -dart run ffigen +dart run ffigen --config config.yaml On linux you may need to install clang 11 dev tools. If you are using apt-get you can do: ``` diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 43c2a0340..2b96a4341 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -2946,6 +2946,32 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer)>(); + void realm_dart_async_open_task_completion_callback( + ffi.Pointer userdata, + ffi.Pointer realm, + ffi.Pointer error, + ) { + return _realm_dart_async_open_task_completion_callback( + userdata, + realm, + error, + ); + } + + late final _realm_dart_async_open_task_completion_callbackPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>( + 'realm_dart_async_open_task_completion_callback'); + late final _realm_dart_async_open_task_completion_callback = + _realm_dart_async_open_task_completion_callbackPtr.asFunction< + void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>(); + Dart_FinalizableHandle realm_dart_attach_finalizer( Object handle, ffi.Pointer realmPtr, @@ -8948,6 +8974,14 @@ class RealmLibrary { class _SymbolAddresses { final RealmLibrary _library; _SymbolAddresses(this._library); + ffi.Pointer< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>> + get realm_dart_async_open_task_completion_callback => + _library._realm_dart_async_open_task_completion_callbackPtr; ffi.Pointer< ffi.NativeFunction< Dart_FinalizableHandle Function( diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 8737e6700..cc66eceb6 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1622,6 +1622,28 @@ class _RealmCore { "Delete user failed"); return completer.future; } + + static void _openRealmAsync(Handle userdata, Pointer realm, Pointer errorCode) { + final completer = userdata as Completer; + + if (errorCode != nullptr) { + completer.completeError(RealmException(errorCode.toString())); + } else { + completer.complete(); + } + } + + Future openRealmAsync(Configuration config) { + final configHandle = _createConfig(config); + final realmAsyncOpenTaskPtr = _realmLib.realm_open_synchronized(configHandle._pointer); + final completer = Completer(); + final callback = Pointer.fromFunction, Pointer)>(_openRealmAsync); + final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); + + _realmLib.realm_async_open_task_start(realmAsyncOpenTaskPtr, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), + _realmLib.addresses.realm_dart_userdata_async_free); + return completer.future; + } } class LastError { diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index 33174a8d5..05ea779a5 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -138,3 +138,26 @@ RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userda (reinterpret_cast(ud->dart_callback))(ud->handle, state); }); } + +RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t userdata, realm_thread_safe_reference_t* realm, realm_async_error_t* error) +{ + struct realm_dart_async_error : realm_async_error + { + realm_dart_async_error(const realm_async_error& error) + : realm_async_error(*error.clone()) + { + + } + }; + + std::unique_ptr error_copy; + if (error != nullptr) { + error_copy = std::make_unique(*error); + } + + + auto ud = reinterpret_cast(userdata); + ud->scheduler->invoke([ud, realm, error = std::move(error_copy)]() { + (reinterpret_cast(ud->dart_callback))(ud->handle, realm, error.get()); + }); +} diff --git a/src/realm_dart_sync.h b/src/realm_dart_sync.h index 0c04c63c3..d0436aca5 100644 --- a/src/realm_dart_sync.h +++ b/src/realm_dart_sync.h @@ -35,3 +35,5 @@ RLM_API void realm_dart_sync_connection_state_changed_callback(realm_userdata_t realm_sync_connection_state_e new_state); RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userdata_t userdata, realm_flx_sync_subscription_set_state_e state); + +RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t userdata, realm_thread_safe_reference_t* realm, realm_async_error_t* error); \ No newline at end of file From dcd479b995c60f1cb3bab17a36455fa5ba6d16d2 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 31 Jul 2022 00:10:13 +0300 Subject: [PATCH 002/113] Convert thread_safe_reference to realm --- lib/src/native/realm_core.dart | 13 ++++++++----- lib/src/realm_class.dart | 23 ++++++++++++++++++----- test/realm_test.dart | 19 +++++++++++++++++++ 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index cc66eceb6..5d6ab46e5 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1624,19 +1624,22 @@ class _RealmCore { } static void _openRealmAsync(Handle userdata, Pointer realm, Pointer errorCode) { - final completer = userdata as Completer; + final completer = userdata as Completer; if (errorCode != nullptr) { completer.completeError(RealmException(errorCode.toString())); } else { - completer.complete(); + final realmPtr = + _realmLib.invokeGetPointer(() => _realmLib.realm_from_thread_safe_reference(realm, scheduler.handle._pointer), "Error opening synchronized realm"); + completer.complete(RealmHandle._(realmPtr)); } } - Future openRealmAsync(Configuration config) { + Future openRealmAsync(Configuration config) { final configHandle = _createConfig(config); - final realmAsyncOpenTaskPtr = _realmLib.realm_open_synchronized(configHandle._pointer); - final completer = Completer(); + final realmAsyncOpenTaskPtr = + _realmLib.invokeGetPointer(() => _realmLib.realm_open_synchronized(configHandle._pointer), "Error opening synchronized realm at path ${config.path}"); + final completer = Completer(); final callback = Pointer.fromFunction, Pointer)>(_openRealmAsync); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index e1ccf13a5..dc0bb6482 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -94,11 +94,24 @@ class Realm { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? _openRealm(config) { + Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? _openRealmSync(config) { _populateMetadata(); } - static RealmHandle _openRealm(Configuration config) { + /// Opens a `Realm` async using a [Configuration] object. + static Future open(Configuration config) async { + var dir = File(config.path).parent; + if (!await dir.exists()) { + await dir.create(recursive: true); + } + RealmHandle handle = await realmCore.openRealmAsync(config); + return Realm._(config, handle); + } + + /// Opens a `Realm` using a [Configuration] object. + static Realm openSync(Configuration config) => Realm._(config); + + static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { dir.createSync(recursive: true); @@ -327,9 +340,9 @@ class Realm { ..level = RealmLogLevel.info ..onRecord.listen((event) => print(event)); - /// 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. + /// 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/test/realm_test.dart b/test/realm_test.dart index f0dd9c653..05bd055b9 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -620,4 +620,23 @@ Future main([List? args]) async { // We should not be in transaction here expect(realm.isInTransaction, false); }); + + test('Realm open async with local configuration throws', () async { + var config = Configuration.local([Car.schema, Person.schema]); + expect(() async => await Realm.open(config), throws("This method is only available for fully synchronized Realms")); + }); + + baasTest('Realm open async', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [ + Task.schema, + Schedule.schema, + Event.schema, + ]); + + final realm = await Realm.open(configuration); + realm.close(); + }); } From 12ccd5b63353af0d12c96c07a0206e36f29b1b96 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 31 Jul 2022 00:41:11 +0300 Subject: [PATCH 003/113] Realm openSync --- test/realm_test.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/realm_test.dart b/test/realm_test.dart index 05bd055b9..4ba9bb93b 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -621,6 +621,12 @@ Future main([List? args]) async { expect(realm.isInTransaction, false); }); + test('Realm openSync', () { + var config = Configuration.local([Car.schema, Person.schema]); + final realm = Realm.openSync(config); + realm.close(); + }); + test('Realm open async with local configuration throws', () async { var config = Configuration.local([Car.schema, Person.schema]); expect(() async => await Realm.open(config), throws("This method is only available for fully synchronized Realms")); From 4c0f6c33138d123fc5e3e2f117fa5c12a05e103d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 11 Aug 2022 11:58:03 +0300 Subject: [PATCH 004/113] Rename _openRealmAsyncCallback --- lib/src/native/realm_core.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index bb3efd768..3406e2439 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1652,7 +1652,7 @@ class _RealmCore { return completer.future; } - static void _openRealmAsync(Handle userdata, Pointer realm, Pointer errorCode) { + static void _openRealmAsyncCallback(Handle userdata, Pointer realm, Pointer errorCode) { final completer = userdata as Completer; if (errorCode != nullptr) { @@ -1669,7 +1669,7 @@ class _RealmCore { final realmAsyncOpenTaskPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open_synchronized(configHandle._pointer), "Error opening synchronized realm at path ${config.path}"); final completer = Completer(); - final callback = Pointer.fromFunction, Pointer)>(_openRealmAsync); + final callback = Pointer.fromFunction, Pointer)>(_openRealmAsyncCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_async_open_task_start(realmAsyncOpenTaskPtr, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), From 9e73ed2f7cb83a35220a0932713904238ab4dbed Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 11 Aug 2022 12:01:37 +0300 Subject: [PATCH 005/113] Remove Realm.openSync --- lib/src/realm_class.dart | 3 --- test/realm_test.dart | 6 ------ 2 files changed, 9 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index dc0bb6482..94d7a3e82 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -108,9 +108,6 @@ class Realm { return Realm._(config, handle); } - /// Opens a `Realm` using a [Configuration] object. - static Realm openSync(Configuration config) => Realm._(config); - static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { diff --git a/test/realm_test.dart b/test/realm_test.dart index 4ba9bb93b..8206356cd 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -620,12 +620,6 @@ Future main([List? args]) async { // We should not be in transaction here expect(realm.isInTransaction, false); }); - - test('Realm openSync', () { - var config = Configuration.local([Car.schema, Person.schema]); - final realm = Realm.openSync(config); - realm.close(); - }); test('Realm open async with local configuration throws', () async { var config = Configuration.local([Car.schema, Person.schema]); From c801ccf7edfa3aa15027cc072a2f07e31cd201ea Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 11 Aug 2022 16:15:45 +0300 Subject: [PATCH 006/113] Cancel operation realm open asyn --- lib/src/native/realm_core.dart | 13 +++++++++---- lib/src/realm_class.dart | 18 +++++++++++++----- pubspec.yaml | 1 + test/realm_test.dart | 8 +++++--- 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 3406e2439..c1628d50c 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -27,6 +27,7 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; +import 'package:async/async.dart'; import '../app.dart'; import '../collections.dart'; @@ -1653,7 +1654,7 @@ class _RealmCore { } static void _openRealmAsyncCallback(Handle userdata, Pointer realm, Pointer errorCode) { - final completer = userdata as Completer; + final completer = userdata as Completer; if (errorCode != nullptr) { completer.completeError(RealmException(errorCode.toString())); @@ -1664,17 +1665,21 @@ class _RealmCore { } } - Future openRealmAsync(Configuration config) { + CancelableOperation openRealmAsync(Configuration config) { final configHandle = _createConfig(config); final realmAsyncOpenTaskPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open_synchronized(configHandle._pointer), "Error opening synchronized realm at path ${config.path}"); - final completer = Completer(); + final completer = Completer(); + final callback = Pointer.fromFunction, Pointer)>(_openRealmAsyncCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_async_open_task_start(realmAsyncOpenTaskPtr, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - return completer.future; + + CancelableOperation cancelOperation = + CancelableOperation.fromFuture(completer.future, onCancel: () => {_realmLib.realm_async_open_task_cancel(realmAsyncOpenTaskPtr)}); + return cancelOperation; } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 94d7a3e82..5f1b621a7 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -21,6 +21,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; +import 'package:async/async.dart'; import 'configuration.dart'; import 'list.dart'; @@ -99,13 +100,20 @@ class Realm { } /// Opens a `Realm` async using a [Configuration] object. - static Future open(Configuration config) async { + static CancelableOperation open(Configuration config) { + var dir = File(config.path).parent; - if (!await dir.exists()) { - await dir.create(recursive: true); + if (!dir.existsSync()) { + dir.createSync(recursive: true); } - RealmHandle handle = await realmCore.openRealmAsync(config); - return Realm._(config, handle); + + var openRealmAsyncOperation = realmCore.openRealmAsync(config); + CancelableOperation cancelableOperation = + CancelableOperation.fromFuture(openRealmAsyncOperation.valueOrCancellation(null).then((handle) => Realm._(config, handle)), + onCancel: () => { + if (!openRealmAsyncOperation.isCompleted) {openRealmAsyncOperation.cancel()} + }); + return cancelableOperation; } static RealmHandle _openRealmSync(Configuration config) { diff --git a/pubspec.yaml b/pubspec.yaml index a2e2340bc..f88e72f16 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 + async: ^2.9.0 dev_dependencies: build_cli: ^2.1.3 diff --git a/test/realm_test.dart b/test/realm_test.dart index 8206356cd..faeb3cf8a 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -19,6 +19,7 @@ // ignore_for_file: unused_local_variable, avoid_relative_lib_imports import 'dart:io'; +import 'dart:js_util'; import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; @@ -620,10 +621,10 @@ Future main([List? args]) async { // We should not be in transaction here expect(realm.isInTransaction, false); }); - + test('Realm open async with local configuration throws', () async { var config = Configuration.local([Car.schema, Person.schema]); - expect(() async => await Realm.open(config), throws("This method is only available for fully synchronized Realms")); + expect(() async => await Realm.open(config).value, throws("This method is only available for fully synchronized Realms")); }); baasTest('Realm open async', (appConfiguration) async { @@ -636,7 +637,8 @@ Future main([List? args]) async { Event.schema, ]); - final realm = await Realm.open(configuration); + final operationOpen = Realm.open(configuration); + final realm = await operationOpen.value; realm.close(); }); } From c0c1f0365fa148b5cd88127b3fcf55623fac6135 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 12 Aug 2022 12:08:52 +0300 Subject: [PATCH 007/113] Create RealmAsyncTask --- lib/src/realm_class.dart | 20 +++++++++++++++++--- test/realm_test.dart | 29 ++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5f1b621a7..72370972b 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -19,6 +19,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/gestures.dart'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:async/async.dart'; @@ -100,8 +101,7 @@ class Realm { } /// Opens a `Realm` async using a [Configuration] object. - static CancelableOperation open(Configuration config) { - + static RealmAsyncTask open(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { dir.createSync(recursive: true); @@ -113,7 +113,7 @@ class Realm { onCancel: () => { if (!openRealmAsyncOperation.isCompleted) {openRealmAsyncOperation.cancel()} }); - return cancelableOperation; + return RealmAsyncTask(cancelableOperation); } static RealmHandle _openRealmSync(Configuration config) { @@ -492,3 +492,17 @@ class RealmLogLevel { /// Same as [Level.OFF]; static const off = Level.OFF; } + +class RealmAsyncTask { + final CancelableOperation _cancelableOperation; + + RealmAsyncTask(CancelableOperation cancelableOperation) : _cancelableOperation = cancelableOperation; + + Future get realm => _cancelableOperation.valueOrCancellation(null); + + void cancel() { + if (!(_cancelableOperation.isCanceled || _cancelableOperation.isCompleted)) { + _cancelableOperation.cancel(); + } + } +} diff --git a/test/realm_test.dart b/test/realm_test.dart index faeb3cf8a..edc8e413d 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -631,14 +631,25 @@ Future main([List? args]) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [ - Task.schema, - Schedule.schema, - Event.schema, - ]); - - final operationOpen = Realm.open(configuration); - final realm = await operationOpen.value; - realm.close(); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + final realmTask = Realm.open(configuration); + final realm = await realmTask.realm; + expect(realm, isNotNull); + if (realm != null) { + realm.close(); + } + }); + + baasTest('Realm open async', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + final realmTask = Realm.open(configuration); + realmTask.cancel(); + final realm = realmTask.realm; + expect(realm, isNull); }); } From ae34d955208abc89f9caf581161d716c7b4c2390 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 12 Aug 2022 13:45:46 +0300 Subject: [PATCH 008/113] Fix test --- test/realm_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index edc8e413d..0df8e9cb0 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -624,7 +624,7 @@ Future main([List? args]) async { test('Realm open async with local configuration throws', () async { var config = Configuration.local([Car.schema, Person.schema]); - expect(() async => await Realm.open(config).value, throws("This method is only available for fully synchronized Realms")); + expect(() async => await Realm.open(config).realm, throws("This method is only available for fully synchronized Realms")); }); baasTest('Realm open async', (appConfiguration) async { From 99f56e8b3a8f55d802057456b2e0434dda4b1dee Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 12 Aug 2022 14:36:45 +0300 Subject: [PATCH 009/113] Delete not needed imports --- lib/src/realm_class.dart | 29 ++++++++++++++--------------- test/realm_test.dart | 5 ++--- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 72370972b..364a5ccec 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -19,7 +19,6 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/gestures.dart'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:async/async.dart'; @@ -102,26 +101,26 @@ class Realm { /// Opens a `Realm` async using a [Configuration] object. static RealmAsyncTask open(Configuration config) { - var dir = File(config.path).parent; - if (!dir.existsSync()) { - dir.createSync(recursive: true); - } + _createFileDirectory(config.path); + CancelableOperation openRealmAsyncOperation = realmCore.openRealmAsync(config); - var openRealmAsyncOperation = realmCore.openRealmAsync(config); - CancelableOperation cancelableOperation = - CancelableOperation.fromFuture(openRealmAsyncOperation.valueOrCancellation(null).then((handle) => Realm._(config, handle)), - onCancel: () => { - if (!openRealmAsyncOperation.isCompleted) {openRealmAsyncOperation.cancel()} - }); + CancelableOperation cancelableOperation = CancelableOperation.fromFuture( + openRealmAsyncOperation.valueOrCancellation(null).then((handle) => handle != null ? Realm._(config, handle) : null), + onCancel: () => { + if (!(openRealmAsyncOperation.isCompleted || openRealmAsyncOperation.isCanceled)) openRealmAsyncOperation.cancel()}); return RealmAsyncTask(cancelableOperation); } static RealmHandle _openRealmSync(Configuration config) { - var dir = File(config.path).parent; + _createFileDirectory(config.path); + return realmCore.openRealm(config); + } + + static void _createFileDirectory(String filePath) { + var dir = File(filePath).parent; if (!dir.existsSync()) { dir.createSync(recursive: true); } - return realmCore.openRealm(config); } void _populateMetadata() { @@ -494,9 +493,9 @@ class RealmLogLevel { } class RealmAsyncTask { - final CancelableOperation _cancelableOperation; + final CancelableOperation _cancelableOperation; - RealmAsyncTask(CancelableOperation cancelableOperation) : _cancelableOperation = cancelableOperation; + RealmAsyncTask(CancelableOperation cancelableOperation) : _cancelableOperation = cancelableOperation; Future get realm => _cancelableOperation.valueOrCancellation(null); diff --git a/test/realm_test.dart b/test/realm_test.dart index 0df8e9cb0..054100cae 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -19,7 +19,6 @@ // ignore_for_file: unused_local_variable, avoid_relative_lib_imports import 'dart:io'; -import 'dart:js_util'; import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; @@ -641,7 +640,7 @@ Future main([List? args]) async { } }); - baasTest('Realm open async', (appConfiguration) async { + baasTest('Realm open async and cancel', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -649,7 +648,7 @@ Future main([List? args]) async { final realmTask = Realm.open(configuration); realmTask.cancel(); - final realm = realmTask.realm; + final realm = await realmTask.realm; expect(realm, isNull); }); } From 929f3084e73257c6e7315d9804a3bc8099420463 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 12 Aug 2022 18:10:11 +0300 Subject: [PATCH 010/113] Rename class RealmAsyncOpenTask --- lib/src/native/realm_core.dart | 5 +++-- lib/src/realm_class.dart | 13 +++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index e24d256fb..3d7d06e2a 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1684,8 +1684,9 @@ class _RealmCore { _realmLib.realm_async_open_task_start(realmAsyncOpenTaskPtr, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - CancelableOperation cancelOperation = - CancelableOperation.fromFuture(completer.future, onCancel: () => {_realmLib.realm_async_open_task_cancel(realmAsyncOpenTaskPtr)}); + CancelableOperation cancelOperation = CancelableOperation.fromFuture(completer.future, onCancel: () { + _realmLib.realm_async_open_task_cancel(realmAsyncOpenTaskPtr); + }); return cancelOperation; } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 364a5ccec..9ee86bdac 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -100,15 +100,14 @@ class Realm { } /// Opens a `Realm` async using a [Configuration] object. - static RealmAsyncTask open(Configuration config) { + static RealmAsyncOpenTask open(Configuration config) { _createFileDirectory(config.path); CancelableOperation openRealmAsyncOperation = realmCore.openRealmAsync(config); CancelableOperation cancelableOperation = CancelableOperation.fromFuture( openRealmAsyncOperation.valueOrCancellation(null).then((handle) => handle != null ? Realm._(config, handle) : null), - onCancel: () => { - if (!(openRealmAsyncOperation.isCompleted || openRealmAsyncOperation.isCanceled)) openRealmAsyncOperation.cancel()}); - return RealmAsyncTask(cancelableOperation); + onCancel: () => {if (!(openRealmAsyncOperation.isCompleted || openRealmAsyncOperation.isCanceled)) openRealmAsyncOperation.cancel()}); + return RealmAsyncOpenTask(cancelableOperation); } static RealmHandle _openRealmSync(Configuration config) { @@ -492,10 +491,10 @@ class RealmLogLevel { static const off = Level.OFF; } -class RealmAsyncTask { +class RealmAsyncOpenTask { final CancelableOperation _cancelableOperation; - RealmAsyncTask(CancelableOperation cancelableOperation) : _cancelableOperation = cancelableOperation; + RealmAsyncOpenTask(CancelableOperation cancelableOperation) : _cancelableOperation = cancelableOperation; Future get realm => _cancelableOperation.valueOrCancellation(null); @@ -504,4 +503,6 @@ class RealmAsyncTask { _cancelableOperation.cancel(); } } + + void addProgressNotificationCallback() {} } From 78a362ea31c78f814f9f25a514fabc066bf36961 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 14:12:19 +0300 Subject: [PATCH 011/113] Implement cancel and onProgress --- lib/src/native/realm_core.dart | 56 ++++++++++++++++++++++++++-------- lib/src/realm_class.dart | 48 ++++++++++++++++++++--------- src/realm_dart_sync.cpp | 2 +- test/realm_test.dart | 21 +++++++++++-- 4 files changed, 96 insertions(+), 31 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 3d7d06e2a..3c72ccb02 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1662,32 +1662,58 @@ class _RealmCore { static void _openRealmAsyncCallback(Handle userdata, Pointer realm, Pointer errorCode) { final completer = userdata as Completer; - if (errorCode != nullptr) { - completer.completeError(RealmException(errorCode.toString())); + { + completer.completeError(RealmException(errorCode.toString())); + } } else { final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_from_thread_safe_reference(realm, scheduler.handle._pointer), "Error opening synchronized realm"); - completer.complete(RealmHandle._(realmPtr)); + if (completer.isCompleted) { + //Task is canceled before to complete but the realm was created. + _realmLib.invokeGetBool(() => _realmLib.realm_close(realmPtr), "Realm close failed"); + } else { + completer.complete(RealmHandle._(realmPtr)); + } } } - CancelableOperation openRealmAsync(Configuration config) { + Future openRealmAsync(RealmAsyncOpenTaskHandle handle, Completer completer) { + final callback = Pointer.fromFunction, Pointer)>(_openRealmAsyncCallback); + final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); + + _realmLib.realm_async_open_task_start(handle._pointer, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), + _realmLib.addresses.realm_dart_userdata_async_free); + return completer.future; + } + + RealmAsyncOpenTaskHandle createRealmAsyncOpenTask(Configuration config) { final configHandle = _createConfig(config); final realmAsyncOpenTaskPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_open_synchronized(configHandle._pointer), "Error opening synchronized realm at path ${config.path}"); - final completer = Completer(); + return RealmAsyncOpenTaskHandle._(realmAsyncOpenTaskPtr); + } - final callback = Pointer.fromFunction, Pointer)>(_openRealmAsyncCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); + void cancelRealmAsyncOpenTask(RealmAsyncOpenTaskHandle handle) { + _realmLib.realm_async_open_task_cancel(handle._pointer); + } - _realmLib.realm_async_open_task_start(realmAsyncOpenTaskPtr, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free); + static void _realmAsyncOpenRegisterProgressNotifierCallback(Handle userdata, int transferredBytes, int totalBytes) { + final realmAsyncOpenTask = userdata as RealmAsyncOpenTask; + if (realmAsyncOpenTask.progressCallback != null) { + realmAsyncOpenTask.progressCallback!(transferredBytes, totalBytes); + } + } - CancelableOperation cancelOperation = CancelableOperation.fromFuture(completer.future, onCancel: () { - _realmLib.realm_async_open_task_cancel(realmAsyncOpenTaskPtr); - }); - return cancelOperation; + int realmAsyncOpenRegisterProgressNotifier(RealmAsyncOpenTask task) { + final callback = Pointer.fromFunction(_realmAsyncOpenRegisterProgressNotifierCallback); + final userdata = _realmLib.realm_dart_userdata_async_new(task, callback.cast(), scheduler.handle._pointer); + return _realmLib.realm_async_open_task_register_download_progress_notifier( + task.handle._pointer, _realmLib.addresses.realm_dart_sync_progress_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); + } + + void realmAsyncOpenUnregisterProgressNotifier(RealmAsyncOpenTaskHandle handle, int token) { + _realmLib.realm_async_open_task_unregister_download_progress_notifier(handle._pointer, token); } } @@ -1734,6 +1760,10 @@ class RealmHandle extends HandleBase { RealmHandle._unowned(Pointer pointer) : super.unowned(pointer); } +class RealmAsyncOpenTaskHandle extends HandleBase { + RealmAsyncOpenTaskHandle._(Pointer pointer) : super(pointer, 24); +} + class SchedulerHandle extends HandleBase { SchedulerHandle._(Pointer pointer) : super(pointer, 24); } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 9ee86bdac..1b8b9c756 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -100,14 +100,14 @@ class Realm { } /// Opens a `Realm` async using a [Configuration] object. - static RealmAsyncOpenTask open(Configuration config) { + static RealmAsyncOpenTask open(Configuration config, {ProgressCallback? onProgressCallback}) { _createFileDirectory(config.path); - CancelableOperation openRealmAsyncOperation = realmCore.openRealmAsync(config); - - CancelableOperation cancelableOperation = CancelableOperation.fromFuture( - openRealmAsyncOperation.valueOrCancellation(null).then((handle) => handle != null ? Realm._(config, handle) : null), - onCancel: () => {if (!(openRealmAsyncOperation.isCompleted || openRealmAsyncOperation.isCanceled)) openRealmAsyncOperation.cancel()}); - return RealmAsyncOpenTask(cancelableOperation); + RealmAsyncOpenTaskHandle realmAsyncOpenTaskHandle = realmCore.createRealmAsyncOpenTask(config); + final completer = Completer(); + Future realm = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).then((handle) { + return handle != null ? Realm._(config, handle) : null; + }); + return RealmAsyncOpenTask._(realmAsyncOpenTaskHandle, config, realm, onProgressCallback, completer); } static RealmHandle _openRealmSync(Configuration config) { @@ -491,18 +491,38 @@ class RealmLogLevel { static const off = Level.OFF; } +typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); + class RealmAsyncOpenTask { - final CancelableOperation _cancelableOperation; + final RealmAsyncOpenTaskHandle _handle; + final Configuration _config; + final Future _realm; + late int _progressToken; + final ProgressCallback? progressCallback; + final Completer _completer; + + RealmAsyncOpenTask._(this._handle, this._config, this._realm, this.progressCallback, this._completer) { + if (progressCallback != null) { + _progressToken = realmCore.realmAsyncOpenRegisterProgressNotifier(this); + } + } - RealmAsyncOpenTask(CancelableOperation cancelableOperation) : _cancelableOperation = cancelableOperation; + RealmAsyncOpenTaskHandle get handle => _handle; - Future get realm => _cancelableOperation.valueOrCancellation(null); + Completer get completer => _completer; + + Configuration get config => _config; + + Future get realm => _realm.whenComplete(() { + if (progressCallback != null) { + realmCore.realmAsyncOpenUnregisterProgressNotifier(_handle, _progressToken); + } + }); void cancel() { - if (!(_cancelableOperation.isCanceled || _cancelableOperation.isCompleted)) { - _cancelableOperation.cancel(); + realmCore.cancelRealmAsyncOpenTask(_handle); + if (!_completer.isCompleted) { + _completer.complete(null); } } - - void addProgressNotificationCallback() {} } diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index 05ea779a5..f3b70fae3 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -160,4 +160,4 @@ RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t use ud->scheduler->invoke([ud, realm, error = std::move(error_copy)]() { (reinterpret_cast(ud->dart_callback))(ud->handle, realm, error.get()); }); -} +} \ No newline at end of file diff --git a/test/realm_test.dart b/test/realm_test.dart index 054100cae..1a85580c9 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -634,8 +634,24 @@ Future main([List? args]) async { final realmTask = Realm.open(configuration); final realm = await realmTask.realm; - expect(realm, isNotNull); if (realm != null) { + expect(realm.isClosed, false); + realm.close(); + } + }); + + baasTest('Realm open async and get progress', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + final realmTask = Realm.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { + print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); + }); + final realm = await realmTask.realm; + if (realm != null) { + expect(realm.isClosed, false); realm.close(); } }); @@ -648,7 +664,6 @@ Future main([List? args]) async { final realmTask = Realm.open(configuration); realmTask.cancel(); - final realm = await realmTask.realm; - expect(realm, isNull); + expect(await realmTask.realm, isNull); }); } From 18aa5485fac5341cea9345b6f61b9b3760ee956d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 14:57:50 +0300 Subject: [PATCH 012/113] Add API doc --- lib/src/realm_class.dart | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 1b8b9c756..5f319d036 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -99,7 +99,18 @@ class Realm { _populateMetadata(); } - /// Opens a `Realm` async using a [Configuration] object. + /// A method for asynchronously obtaining and opening a [Realm]. + /// + /// If the configuration is [FlexibleSyncConfiguration], the realm will be downloaded and fully + /// synchronized with the server prior to the completion of the returned [RealmAsyncOpenTask]. + /// Otherwise this method will throw an exception. + /// + /// Open realm async arguments are: + /// * `config`- a configuration object that describes the realm. + /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications + /// + /// The returned task has an awaitable [RealmAsyncOpenTask.realm] future that is completed once the remote realm is fully synchronized. + /// It provides a method [RealmAsyncOpenTask.cancel] that cancels any current running download. static RealmAsyncOpenTask open(Configuration config, {ProgressCallback? onProgressCallback}) { _createFileDirectory(config.path); RealmAsyncOpenTaskHandle realmAsyncOpenTaskHandle = realmCore.createRealmAsyncOpenTask(config); @@ -491,8 +502,25 @@ class RealmLogLevel { static const off = Level.OFF; } +/// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. +/// This is the registered callback [onProgressCallback] to receive progress notifications while the download is in progress. +/// +/// It is called with the following arguments: +/// * `transferredBytes` - the current number of bytes already transferred +/// * `totalBytes` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer) +/// {@category Realm} typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); +/// A task object which can be used to await for a realm to open async or to cancel it. +/// +/// When a synchronized Realm is opened asynchronously, +/// the latest state of the Realm is downloaded from the server before the completion callback is invoked. +/// This task object can be used to await the the download process to complete or to cancel it. +/// A download progress notifier is registered is case of having [ProgressCallback], +/// which allows you to observe the state of the download. +/// Once the process completes the download progress notifier is unregistered. +/// +/// {@category Realm} class RealmAsyncOpenTask { final RealmAsyncOpenTaskHandle _handle; final Configuration _config; @@ -509,16 +537,21 @@ class RealmAsyncOpenTask { RealmAsyncOpenTaskHandle get handle => _handle; - Completer get completer => _completer; - + /// The configuration object that describes the realm. Configuration get config => _config; + /// An awaitable future that is completed once + /// the remote realm is fully synchronized or download is canceled. + /// Returns null when the process is canceled. Future get realm => _realm.whenComplete(() { if (progressCallback != null) { realmCore.realmAsyncOpenUnregisterProgressNotifier(_handle, _progressToken); } }); + /// Cancels any current running download. + /// If multiple [RealmAsyncOpenTask] are all in the progress for the same Realm, + /// then canceling one of them will cancel all of them. void cancel() { realmCore.cancelRealmAsyncOpenTask(_handle); if (!_completer.isCompleted) { From 1f2403f3aab29fd365ef91984607d589df7d06f8 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 15:13:06 +0300 Subject: [PATCH 013/113] Fix API doc --- lib/src/realm_class.dart | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5f319d036..9345723e5 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -107,7 +107,7 @@ class Realm { /// /// Open realm async arguments are: /// * `config`- a configuration object that describes the realm. - /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications + /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications. It is not mandatory. /// /// The returned task has an awaitable [RealmAsyncOpenTask.realm] future that is completed once the remote realm is fully synchronized. /// It provides a method [RealmAsyncOpenTask.cancel] that cancels any current running download. @@ -511,13 +511,13 @@ class RealmLogLevel { /// {@category Realm} typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); -/// A task object which can be used to await for a realm to open async or to cancel it. +/// Represents a task object which can be used to await for a realm to open asynchronously or to cancel opening. /// -/// When a synchronized Realm is opened asynchronously, +/// When a [Realm] with [FlexibleSyncConfiguration] is opened asynchronously, /// the latest state of the Realm is downloaded from the server before the completion callback is invoked. -/// This task object can be used to await the the download process to complete or to cancel it. +/// This task object can be used to await the download process to complete or to cancel downloading. /// A download progress notifier is registered is case of having [ProgressCallback], -/// which allows you to observe the state of the download. +/// which allows you to observe the state of the download progress. /// Once the process completes the download progress notifier is unregistered. /// /// {@category Realm} @@ -526,16 +526,18 @@ class RealmAsyncOpenTask { final Configuration _config; final Future _realm; late int _progressToken; - final ProgressCallback? progressCallback; + final ProgressCallback? _progressCallback; final Completer _completer; - RealmAsyncOpenTask._(this._handle, this._config, this._realm, this.progressCallback, this._completer) { - if (progressCallback != null) { + RealmAsyncOpenTask._(this._handle, this._config, this._realm, this._progressCallback, this._completer) { + if (_progressCallback != null) { _progressToken = realmCore.realmAsyncOpenRegisterProgressNotifier(this); } } - RealmAsyncOpenTaskHandle get handle => _handle; + /// This is the registered callback to receive progress notifications + /// while the download is in progress. + ProgressCallback? get progressCallback => _progressCallback; /// The configuration object that describes the realm. Configuration get config => _config; From 640eece19377f0f9762cd9f72cf4012432aeb1a0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 15:16:50 +0300 Subject: [PATCH 014/113] Update CHANGELOG.md --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79d2c6b92..08af9edb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,9 @@ * Support `App.deleteUser ` for deleting user accounts. ([#679](https://github.com/realm/realm-dart/pull/679)) * Support Apple, Facebook and Google authentication. ([#740](https://github.com/realm/realm-dart/pull/740)) * Allow multiple anonymous sessions. When using anonymous authentication you can now easily log in with a different anonymous user than last time. ([#750](https://github.com/realm/realm-dart/pull/750)). -* Support `Credentials.jwt` for login user with JWT issued by custom provider . ([#715](https://github.com/realm/realm-dart/pull/715)) +* Support `Credentials.jwt` for login user with JWT issued by custom provider. ([#715](https://github.com/realm/realm-dart/pull/715)) +* Support `Realm.open` API to asynchronously open a synchronized Realm. It will download all remote content available at the time the operation began on a background thread and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) + ### Internal * Added a command to `realm_dart` for deleting Atlas App Services applications. Usage: `dart run realm_dart delete-apps`. By default it will delete apps from `http://localhost:9090` which is the endpoint of the local docker image. If `--atlas-cluster` is provided, it will authenticate, delete the application from the provided cluster. (PR [#663](https://github.com/realm/realm-dart/pull/663)) From c0c88eb7081daa9e8ba535ba4a3cf928b42e7405 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 15:27:20 +0300 Subject: [PATCH 015/113] Fix removed handle from RealmAsyncOpenTask --- lib/src/realm_class.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 9345723e5..fb3aca25f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -525,7 +525,7 @@ class RealmAsyncOpenTask { final RealmAsyncOpenTaskHandle _handle; final Configuration _config; final Future _realm; - late int _progressToken; + late int _progressToken; final ProgressCallback? _progressCallback; final Completer _completer; @@ -535,6 +535,8 @@ class RealmAsyncOpenTask { } } + RealmAsyncOpenTaskHandle get handle => _handle; + /// This is the registered callback to receive progress notifications /// while the download is in progress. ProgressCallback? get progressCallback => _progressCallback; From 427501b20d831c3fa2eee26b80275c38c1d93cb3 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 16:07:39 +0300 Subject: [PATCH 016/113] open async many times exception --- lib/src/realm_class.dart | 7 +++++-- src/realm_dart_sync.cpp | 1 + test/realm_test.dart | 13 +++++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index fb3aca25f..6f9407529 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -115,7 +115,10 @@ class Realm { _createFileDirectory(config.path); RealmAsyncOpenTaskHandle realmAsyncOpenTaskHandle = realmCore.createRealmAsyncOpenTask(config); final completer = Completer(); - Future realm = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).then((handle) { + Future realm = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).onError((error, stackTrace) { + print(error); + return null; + }).then((handle) { return handle != null ? Realm._(config, handle) : null; }); return RealmAsyncOpenTask._(realmAsyncOpenTaskHandle, config, realm, onProgressCallback, completer); @@ -525,7 +528,7 @@ class RealmAsyncOpenTask { final RealmAsyncOpenTaskHandle _handle; final Configuration _config; final Future _realm; - late int _progressToken; + late int _progressToken; final ProgressCallback? _progressCallback; final Completer _completer; diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index f3b70fae3..47898a2fc 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -141,6 +141,7 @@ RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userda RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t userdata, realm_thread_safe_reference_t* realm, realm_async_error_t* error) { + //TODO: Read and copy the error with the correct message or code. struct realm_dart_async_error : realm_async_error { realm_dart_async_error(const realm_async_error& error) diff --git a/test/realm_test.dart b/test/realm_test.dart index 1a85580c9..79ff04105 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -666,4 +666,17 @@ Future main([List? args]) async { realmTask.cancel(); expect(await realmTask.realm, isNull); }); + + baasTest('Realm open async many times and cancel once', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + final realmTaskFirst = Realm.open(configuration); + final realmTaskSecond = Realm.open(configuration); + realmTaskFirst.cancel(); + expect(await realmTaskFirst.realm, isNull); + expect(await realmTaskSecond.realm, isNull); + }); } From 49ff150608eeddae4d1ffc07816cd27bf712bbb0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 14 Aug 2022 16:11:51 +0300 Subject: [PATCH 017/113] Delete package dart async --- lib/src/native/realm_core.dart | 3 +-- lib/src/realm_class.dart | 1 - pubspec.yaml | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 28b15fadd..f838d865c 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -27,7 +27,6 @@ import 'dart:typed_data'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; -import 'package:async/async.dart'; import '../app.dart'; import '../collections.dart'; @@ -966,7 +965,7 @@ class _RealmCore { return RealmAppCredentialsHandle._(_realmLib.realm_app_credentials_new_google_auth_code(authCodePtr)); }); } - + RealmAppCredentialsHandle createAppCredentialsFunction(String payload) { return using((arena) { final payloadPtr = payload.toCharPtr(arena); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 6f9407529..687d06b9a 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -21,7 +21,6 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; -import 'package:async/async.dart'; import 'configuration.dart'; import 'list.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 1587b22b3..d0c132d6e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,6 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 - async: ^2.9.0 dev_dependencies: build_cli: ^2.2.0 From 1a8b7d42588916d38d6ee7e3442be4954edc7811 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 17 Aug 2022 23:26:55 +0300 Subject: [PATCH 018/113] Regenerate bindings --- lib/src/native/realm_bindings.dart | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index dfe91759c..4d3b0f023 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -2989,28 +2989,6 @@ class RealmLibrary { ffi.Pointer, ffi.Pointer)>(); - Dart_FinalizableHandle realm_dart_attach_finalizer( - Object handle, - ffi.Pointer realmPtr, - int size, - ) { - return _realm_dart_attach_finalizer( - handle, - realmPtr, - size, - ); - } - - late final _realm_dart_attach_finalizerPtr = _lookup< - ffi.NativeFunction< - Dart_FinalizableHandle Function(ffi.Handle, ffi.Pointer, - ffi.Int)>>('realm_dart_attach_finalizer'); - late final _realm_dart_attach_finalizer = - _realm_dart_attach_finalizerPtr.asFunction< - Dart_FinalizableHandle Function( - Object, ffi.Pointer, int)>(); - - ffi.Pointer realm_dart_create_scheduler( int isolateId, int port, @@ -9604,13 +9582,6 @@ class _SymbolAddresses { _library._realm_dart_async_open_task_completion_callbackPtr; ffi.Pointer< ffi.NativeFunction< - Dart_FinalizableHandle Function( - ffi.Handle, ffi.Pointer, ffi.Int)>> - get realm_dart_attach_finalizer => - _library._realm_dart_attach_finalizerPtr; - ffi.Pointer< - ffi.NativeFunction< - ffi.Pointer Function(ffi.Uint64, Dart_Port)>> get realm_dart_create_scheduler => _library._realm_dart_create_schedulerPtr; From a0ab6cf92105d34fe5ea06c03071969a90364b3e Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 19 Aug 2022 17:53:16 +0300 Subject: [PATCH 019/113] Implement RealmCancelationController --- lib/src/native/realm_core.dart | 25 ++++---- lib/src/realm_class.dart | 108 ++++++++++++++++----------------- pubspec.yaml | 1 + test/realm_test.dart | 71 ++++++++++++++++------ 4 files changed, 121 insertions(+), 84 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 7ee4954f6..84512ce91 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1681,12 +1681,15 @@ class _RealmCore { return completer.future; } - static void _openRealmAsyncCallback(Handle userdata, Pointer realm, Pointer errorCode) { + static void _openRealmAsyncCallback(Handle userdata, Pointer realm, Pointer error) { final completer = userdata as Completer; - if (errorCode != nullptr) { - { - completer.completeError(RealmException(errorCode.toString())); - } + if (error != nullptr) { + using((Arena arena) { + final out_error = arena(); + _realmLib.realm_get_async_error(error, out_error); + final message = out_error.ref.message.cast().toRealmDartString(); + completer.completeError(RealmException(message!)); + }); } else { final realmPtr = _realmLib.invokeGetPointer(() => _realmLib.realm_from_thread_safe_reference(realm, scheduler.handle._pointer), "Error opening synchronized realm"); @@ -1720,17 +1723,15 @@ class _RealmCore { } static void _realmAsyncOpenRegisterProgressNotifierCallback(Handle userdata, int transferredBytes, int totalBytes) { - final realmAsyncOpenTask = userdata as RealmAsyncOpenTask; - if (realmAsyncOpenTask.progressCallback != null) { - realmAsyncOpenTask.progressCallback!(transferredBytes, totalBytes); - } + final progressCallback = userdata as void Function(int transferredBytes, int totalBytes); + progressCallback(transferredBytes, totalBytes); } - int realmAsyncOpenRegisterProgressNotifier(RealmAsyncOpenTask task) { + int realmAsyncOpenRegisterProgressNotifier(RealmAsyncOpenTaskHandle handle, void Function(int transferredBytes, int totalBytes) progressCallback) { final callback = Pointer.fromFunction(_realmAsyncOpenRegisterProgressNotifierCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(task, callback.cast(), scheduler.handle._pointer); + final userdata = _realmLib.realm_dart_userdata_async_new(progressCallback, callback.cast(), scheduler.handle._pointer); return _realmLib.realm_async_open_task_register_download_progress_notifier( - task.handle._pointer, _realmLib.addresses.realm_dart_sync_progress_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); + handle._pointer, _realmLib.addresses.realm_dart_sync_progress_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); } void realmAsyncOpenUnregisterProgressNotifier(RealmAsyncOpenTaskHandle handle, int token) { diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 456c4d0bf..f6d5bb2a1 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -19,9 +19,9 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; - import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; +import 'package:async/async.dart'; import 'configuration.dart'; import 'list.dart'; @@ -102,26 +102,59 @@ class Realm implements Finalizable { /// A method for asynchronously obtaining and opening a [Realm]. /// /// If the configuration is [FlexibleSyncConfiguration], the realm will be downloaded and fully - /// synchronized with the server prior to the completion of the returned [RealmAsyncOpenTask]. + /// synchronized with the server prior to the completion of the returned [Future]. /// Otherwise this method will throw an exception. /// /// Open realm async arguments are: /// * `config`- a configuration object that describes the realm. + /// * `cancelationController` - an initialized object of [RealmCancelationController] that is used to cancel the operation. It is not mandatory. /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications. It is not mandatory. /// - /// The returned task has an awaitable [RealmAsyncOpenTask.realm] future that is completed once the remote realm is fully synchronized. - /// It provides a method [RealmAsyncOpenTask.cancel] that cancels any current running download. - static RealmAsyncOpenTask open(Configuration config, {ProgressCallback? onProgressCallback}) { + /// The returned type is [Future] that is completed once the remote realm is fully synchronized or canceled. + /// [RealmCancelationController] provides method [cancel] that cancels any current running download. + /// If multiple [Realm.open] operations are all in the progress for the same Realm, + /// then canceling one of them will cancel all of them. + static Future open(Configuration config, {RealmCancelationController? cancelationController, ProgressCallback? onProgressCallback}) { _createFileDirectory(config.path); + + if (cancelationController != null) { + if (cancelationController._canceledCalled) { + return Future.value(null); + } + if (cancelationController._opration != null) { + throw RealmException("RealmCancelableOperation is already in use."); + } + } + RealmAsyncOpenTaskHandle realmAsyncOpenTaskHandle = realmCore.createRealmAsyncOpenTask(config); - final completer = Completer(); - Future realm = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).onError((error, stackTrace) { - print(error); - return null; + + int progressToken = 0; + if (onProgressCallback != null) { + progressToken = realmCore.realmAsyncOpenRegisterProgressNotifier(realmAsyncOpenTaskHandle, onProgressCallback); + } + + Completer completer = Completer(); + Future realmFuture = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).onError((error, stackTrace) { + throw RealmException(error.toString()); }).then((handle) { + if (progressToken > 0) { + realmCore.realmAsyncOpenUnregisterProgressNotifier(realmAsyncOpenTaskHandle, progressToken); + } + cancelationController?._opration = null; return handle != null ? Realm._(config, handle) : null; }); - return RealmAsyncOpenTask._(realmAsyncOpenTaskHandle, config, realm, onProgressCallback, completer); + + var cancelableOperation = CancelableOperation.fromFuture(realmFuture, onCancel: () { + if (!completer.isCompleted) { + realmCore.cancelRealmAsyncOpenTask(realmAsyncOpenTaskHandle); + completer.complete(null); + } + }); + + cancelationController?._opration = cancelableOperation; + if (cancelationController != null && cancelationController._canceledCalled) cancelationController.cancel(); + + return cancelableOperation.valueOrCancellation(null); } static RealmHandle _openRealmSync(Configuration config) { @@ -527,7 +560,7 @@ class RealmLogLevel { } /// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. -/// This is the registered callback [onProgressCallback] to receive progress notifications while the download is in progress. +/// This is the registered callback onProgressCallback to receive progress notifications while the download is in progress. /// /// It is called with the following arguments: /// * `transferredBytes` - the current number of bytes already transferred @@ -535,55 +568,22 @@ class RealmLogLevel { /// {@category Realm} typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); -/// Represents a task object which can be used to await for a realm to open asynchronously or to cancel opening. +/// Represents an object containing [CancelableOperation] that allows a [Future] operations to be canceled. /// -/// When a [Realm] with [FlexibleSyncConfiguration] is opened asynchronously, -/// the latest state of the Realm is downloaded from the server before the completion callback is invoked. -/// This task object can be used to await the download process to complete or to cancel downloading. -/// A download progress notifier is registered is case of having [ProgressCallback], -/// which allows you to observe the state of the download progress. -/// Once the process completes the download progress notifier is unregistered. +/// Provides a method cancel that canceles the initiated [CancelableOperation]. +/// The [CancelableOperation] is initiated internaly by the Future method that this object is passed to as an argument. /// /// {@category Realm} -class RealmAsyncOpenTask { - final RealmAsyncOpenTaskHandle _handle; - final Configuration _config; - final Future _realm; - late int _progressToken; - final ProgressCallback? _progressCallback; - final Completer _completer; - - RealmAsyncOpenTask._(this._handle, this._config, this._realm, this._progressCallback, this._completer) { - if (_progressCallback != null) { - _progressToken = realmCore.realmAsyncOpenRegisterProgressNotifier(this); - } - } - - RealmAsyncOpenTaskHandle get handle => _handle; - - /// This is the registered callback to receive progress notifications - /// while the download is in progress. - ProgressCallback? get progressCallback => _progressCallback; +class RealmCancelationController { + bool _canceledCalled = false; - /// The configuration object that describes the realm. - Configuration get config => _config; + CancelableOperation? _opration; - /// An awaitable future that is completed once - /// the remote realm is fully synchronized or download is canceled. - /// Returns null when the process is canceled. - Future get realm => _realm.whenComplete(() { - if (progressCallback != null) { - realmCore.realmAsyncOpenUnregisterProgressNotifier(_handle, _progressToken); - } - }); - - /// Cancels any current running download. - /// If multiple [RealmAsyncOpenTask] are all in the progress for the same Realm, - /// then canceling one of them will cancel all of them. + /// Cancels any running operation. void cancel() { - realmCore.cancelRealmAsyncOpenTask(_handle); - if (!_completer.isCompleted) { - _completer.complete(null); + if (_opration != null) { + _opration!.cancel(); } + _canceledCalled = true; } } diff --git a/pubspec.yaml b/pubspec.yaml index 0cf6bada6..bec27c0f2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 + async: ^2.9.0 dev_dependencies: build_cli: ^2.2.0 diff --git a/test/realm_test.dart b/test/realm_test.dart index 42f64194e..9dd83bc06 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -19,10 +19,8 @@ // ignore_for_file: unused_local_variable, avoid_relative_lib_imports import 'dart:io'; -import 'dart:math'; import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; - import 'test.dart'; Future main([List? args]) async { @@ -716,10 +714,10 @@ Future main([List? args]) async { expect(dan.friends, [alice, bob, carol]); expect(danAgain.isManaged, isFalse); // dan wasn't updated }); - - test('Realm open async with local configuration throws', () async { + + test('Realm open async with local configuration throws', () async { var config = Configuration.local([Car.schema, Person.schema]); - expect(() async => await Realm.open(config).realm, throws("This method is only available for fully synchronized Realms")); + expect(() async => await Realm.open(config), throws("This method is only available for fully synchronized Realms")); }); baasTest('Realm open async', (appConfiguration) async { @@ -728,8 +726,7 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realmTask = Realm.open(configuration); - final realm = await realmTask.realm; + final realm = await Realm.open(configuration); if (realm != null) { expect(realm.isClosed, false); realm.close(); @@ -742,10 +739,9 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realmTask = Realm.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { + final realm = await Realm.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); }); - final realm = await realmTask.realm; if (realm != null) { expect(realm.isClosed, false); realm.close(); @@ -758,9 +754,22 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realmTask = Realm.open(configuration); - realmTask.cancel(); - expect(await realmTask.realm, isNull); + var cancelationController = RealmCancelationController(); + final realm = Realm.open(configuration, cancelationController: cancelationController); + cancelationController.cancel(); + expect(await realm, isNull); + }); + + baasTest('Realm open async with the same cancelationController throws', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancelationController = RealmCancelationController(); + final realm1 = Realm.open(configuration, cancelationController: cancelationController); + expect(() => Realm.open(configuration, cancelationController: cancelationController), throws("RealmCancelableOperation is already in use")); + (await realm1)!.close(); }); baasTest('Realm open async many times and cancel once', (appConfiguration) async { @@ -769,15 +778,41 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realmTaskFirst = Realm.open(configuration); - final realmTaskSecond = Realm.open(configuration); - realmTaskFirst.cancel(); - expect(await realmTaskFirst.realm, isNull); - expect(await realmTaskSecond.realm, isNull); + var cancelationController1 = RealmCancelationController(); + final realm1 = Realm.open(configuration, cancelationController: cancelationController1); + var cancelationController2 = RealmCancelationController(); + final realm2 = Realm.open(configuration, cancelationController: cancelationController2); + cancelationController1.cancel(); + expect(await realm1, isNull); + expect(() async => await realm2, throws("operation canceled")); + }); + + baasTest('RealmCancelableOperation.cancel before initialization also cancel the operation', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancelationController = RealmCancelationController(); + cancelationController.cancel(); + final realm = await Realm.open(configuration, cancelationController: cancelationController); + expect(realm, isNull); + }); + + baasTest('RealmCancelableOperation.cancel after realm is obtained', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancelationController = RealmCancelationController(); + final realm = await Realm.open(configuration, cancelationController: cancelationController); + expect(realm, isNotNull); + cancelationController.cancel(); + realm!.close(); }); } extension _IterableEx on Iterable { Iterable except(T exclude) => where((o) => o != exclude); - } From 91174886e9dfb682e6a125281b31046394e9c6b0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 19 Aug 2022 18:08:23 +0300 Subject: [PATCH 020/113] Fix merging --- lib/src/realm_class.dart | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index beb7d89e4..02a0055e3 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -105,7 +105,7 @@ class Realm implements Finalizable { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? _openRealmSync(config) { + Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? _openRealmSync(config) { _populateMetadata(); } @@ -620,17 +620,9 @@ class DynamicRealm { final accessor = RealmCoreAccessor(metadata); return RealmObjectInternal.create(RealmObject, _realm, handle, accessor); } - - /// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. - /// This is the registered callback [onProgressCallback] to receive progress notifications while the download is in progress. - /// - /// It is called with the following arguments: - /// * `transferredBytes` - the current number of bytes already transferred - /// * `totalBytes` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer) - /// {@category Realm} - typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); +} - /// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. +/// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. /// This is the registered callback onProgressCallback to receive progress notifications while the download is in progress. /// /// It is called with the following arguments: From b8793ace5d609d871b86e63b80b307ff7a478cd7 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 19 Aug 2022 18:56:46 +0300 Subject: [PATCH 021/113] Add test for multiple realms --- test/realm_test.dart | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/realm_test.dart b/test/realm_test.dart index 9dd83bc06..0c9427ef6 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -787,6 +787,27 @@ Future main([List? args]) async { expect(() async => await realm2, throws("operation canceled")); }); + baasTest('Realm open async to different Realms and cancel only the first', (appConfiguration) async { + final app = App(appConfiguration); + + final user1 = await app.logIn(Credentials.anonymous()); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${user1.id}.realm'); + var cancelationController1 = RealmCancelationController(); + final realm1 = Realm.open(configuration1, cancelationController: cancelationController1); + + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${user2.id}.realm'); + var cancelationController2 = RealmCancelationController(); + final realm2 = Realm.open(configuration2, cancelationController: cancelationController2); + + cancelationController1.cancel(); + + expect(await realm1, isNull); + var realm = await realm2; + expect(realm, isNotNull); + realm!.close(); + }); + baasTest('RealmCancelableOperation.cancel before initialization also cancel the operation', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); From b25a3b17e2e786715d41930a3a629567d3b39ae8 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 19 Aug 2022 19:16:44 +0300 Subject: [PATCH 022/113] Test download progress with subscriptions --- lib/src/realm_class.dart | 5 +++-- test/realm_test.dart | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 02a0055e3..fe41c6fe5 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -145,7 +145,8 @@ class Realm implements Finalizable { Completer completer = Completer(); Future realmFuture = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).onError((error, stackTrace) { - throw RealmException(error.toString()); + print(error); + return null; }).then((handle) { if (progressToken > 0) { realmCore.realmAsyncOpenUnregisterProgressNotifier(realmAsyncOpenTaskHandle, progressToken); @@ -649,4 +650,4 @@ class RealmCancelationController { } _canceledCalled = true; } -} \ No newline at end of file +} diff --git a/test/realm_test.dart b/test/realm_test.dart index 0c9427ef6..9d4aa7006 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -748,6 +748,36 @@ Future main([List? args]) async { } }); + baasTest('Realm open async, add data and get progress', (appConfiguration) async { + final app = App(appConfiguration); + + final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${user1.id}.realm'); + final realm1 = Realm(configuration1); + realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); + realm1.close(); + + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${user2.id}.realm'); + final realm2 = Realm(configuration2); + realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + realm2.write(() { + for (var i = 0; i < 100; i++) { + realm2.add(Task(ObjectId())); + } + }); + realm2.close(); + + final realmAsync1 = Realm.open(configuration1, onProgressCallback: (transferredBytes, totalBytes) { + print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); + }); + var syncedRealm = await realmAsync1; + if (syncedRealm != null) { + expect(syncedRealm.isClosed, false); + syncedRealm.close(); + } + }); + baasTest('Realm open async and cancel', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); From 69311a1b5f565e6abe549a2f137c910420b7ee70 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 19 Aug 2022 19:23:19 +0300 Subject: [PATCH 023/113] Fix test --- test/realm_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 9d4aa7006..d9bc49b86 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -814,7 +814,7 @@ Future main([List? args]) async { final realm2 = Realm.open(configuration, cancelationController: cancelationController2); cancelationController1.cancel(); expect(await realm1, isNull); - expect(() async => await realm2, throws("operation canceled")); + expect(await realm2, isNull); }); baasTest('Realm open async to different Realms and cancel only the first', (appConfiguration) async { From 503f882d6cf54149d5747de6d02ea82617b3c827 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 22 Aug 2022 10:48:28 +0300 Subject: [PATCH 024/113] Fixed Realm paths --- test/realm_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index d9bc49b86..0ad6da575 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -752,13 +752,13 @@ Future main([List? args]) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${user1.id}.realm'); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); final realm1 = Realm(configuration1); realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); realm1.close(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${user2.id}.realm'); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); final realm2 = Realm(configuration2); realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); realm2.write(() { @@ -821,12 +821,12 @@ Future main([List? args]) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous()); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${user1.id}.realm'); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); var cancelationController1 = RealmCancelationController(); final realm1 = Realm.open(configuration1, cancelationController: cancelationController1); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${user2.id}.realm'); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); var cancelationController2 = RealmCancelationController(); final realm2 = Realm.open(configuration2, cancelationController: cancelationController2); From adf3113383cf7094d205cedd7cf412020c91f211 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 12 Sep 2022 19:15:06 +0300 Subject: [PATCH 025/113] Remove a comment --- src/realm_dart_sync.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index 47898a2fc..f3b70fae3 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -141,7 +141,6 @@ RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userda RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t userdata, realm_thread_safe_reference_t* realm, realm_async_error_t* error) { - //TODO: Read and copy the error with the correct message or code. struct realm_dart_async_error : realm_async_error { realm_dart_async_error(const realm_async_error& error) From 4c674358189ed3e1e6d2ea9dd06b2509dec21fcd Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 12 Sep 2022 19:40:23 +0300 Subject: [PATCH 026/113] Fixing some condition --- lib/src/realm_class.dart | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 6f223bdc3..88814a279 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -163,7 +163,7 @@ class Realm implements Finalizable { }); cancelationController?._opration = cancelableOperation; - if (cancelationController != null && cancelationController._canceledCalled) cancelationController.cancel(); + if (cancelationController?._canceledCalled == false) cancelationController?.cancel(); return cancelableOperation.valueOrCancellation(null); } @@ -634,7 +634,7 @@ typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); /// Represents an object containing [CancelableOperation] that allows a [Future] operations to be canceled. /// -/// Provides a method cancel that canceles the initiated [CancelableOperation]. +/// Provides a method cancel that cancels the initiated [CancelableOperation]. /// The [CancelableOperation] is initiated internaly by the Future method that this object is passed to as an argument. /// /// {@category Realm} @@ -645,9 +645,7 @@ class RealmCancelationController { /// Cancels any running operation. void cancel() { - if (_opration != null) { - _opration!.cancel(); - } + _opration?.cancel(); _canceledCalled = true; } } From 6b92691b32b3ffda40140aa51c67ca4afa550b31 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 13 Sep 2022 17:39:06 +0300 Subject: [PATCH 027/113] Refactor Realp open async --- flutter/realm_flutter/pubspec.yaml | 1 + lib/src/native/realm_core.dart | 57 ---------------------- lib/src/realm_class.dart | 77 ++++++++---------------------- pubspec.yaml | 2 +- test/realm_test.dart | 76 +++++++++++++---------------- 5 files changed, 54 insertions(+), 159 deletions(-) diff --git a/flutter/realm_flutter/pubspec.yaml b/flutter/realm_flutter/pubspec.yaml index bc45ef87c..1f9383f44 100644 --- a/flutter/realm_flutter/pubspec.yaml +++ b/flutter/realm_flutter/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 + cancellation_token: ^1.4.0 dev_dependencies: flutter_test: diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index b0ba9e9d3..547f51136 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1750,63 +1750,6 @@ class _RealmCore { "Delete user failed"); return completer.future; } - - static void _openRealmAsyncCallback(Handle userdata, Pointer realm, Pointer error) { - final completer = userdata as Completer; - if (error != nullptr) { - using((Arena arena) { - final out_error = arena(); - _realmLib.realm_get_async_error(error, out_error); - final message = out_error.ref.message.cast().toRealmDartString(); - completer.completeError(RealmException(message!)); - }); - } else { - final realmPtr = - _realmLib.invokeGetPointer(() => _realmLib.realm_from_thread_safe_reference(realm, scheduler.handle._pointer), "Error opening synchronized realm"); - if (completer.isCompleted) { - //Task is canceled before to complete but the realm was created. - _realmLib.invokeGetBool(() => _realmLib.realm_close(realmPtr), "Realm close failed"); - } else { - completer.complete(RealmHandle._(realmPtr)); - } - } - } - - Future openRealmAsync(RealmAsyncOpenTaskHandle handle, Completer completer) { - final callback = Pointer.fromFunction, Pointer)>(_openRealmAsyncCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - - _realmLib.realm_async_open_task_start(handle._pointer, _realmLib.addresses.realm_dart_async_open_task_completion_callback, userdata.cast(), - _realmLib.addresses.realm_dart_userdata_async_free); - return completer.future; - } - - RealmAsyncOpenTaskHandle createRealmAsyncOpenTask(Configuration config) { - final configHandle = _createConfig(config); - final realmAsyncOpenTaskPtr = - _realmLib.invokeGetPointer(() => _realmLib.realm_open_synchronized(configHandle._pointer), "Error opening synchronized realm at path ${config.path}"); - return RealmAsyncOpenTaskHandle._(realmAsyncOpenTaskPtr); - } - - void cancelRealmAsyncOpenTask(RealmAsyncOpenTaskHandle handle) { - _realmLib.realm_async_open_task_cancel(handle._pointer); - } - - static void _realmAsyncOpenRegisterProgressNotifierCallback(Handle userdata, int transferredBytes, int totalBytes) { - final progressCallback = userdata as void Function(int transferredBytes, int totalBytes); - progressCallback(transferredBytes, totalBytes); - } - - int realmAsyncOpenRegisterProgressNotifier(RealmAsyncOpenTaskHandle handle, void Function(int transferredBytes, int totalBytes) progressCallback) { - final callback = Pointer.fromFunction(_realmAsyncOpenRegisterProgressNotifierCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(progressCallback, callback.cast(), scheduler.handle._pointer); - return _realmLib.realm_async_open_task_register_download_progress_notifier( - handle._pointer, _realmLib.addresses.realm_dart_sync_progress_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - } - - void realmAsyncOpenUnregisterProgressNotifier(RealmAsyncOpenTaskHandle handle, int token) { - _realmLib.realm_async_open_task_unregister_download_progress_notifier(handle._pointer, token); - } } class LastError { diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 88814a279..3e291a2e0 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -23,7 +23,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:collection/collection.dart'; -import 'package:async/async.dart'; +import 'package:cancellation_token/cancellation_token.dart'; import 'configuration.dart'; import 'list.dart'; @@ -83,6 +83,7 @@ export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState, UserIdentity; export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress, ConnectionStateChange; +export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; /// A [Realm] instance represents a `Realm` database. /// @@ -124,48 +125,26 @@ class Realm implements Finalizable { /// [RealmCancelationController] provides method [cancel] that cancels any current running download. /// If multiple [Realm.open] operations are all in the progress for the same Realm, /// then canceling one of them will cancel all of them. - static Future open(Configuration config, {RealmCancelationController? cancelationController, ProgressCallback? onProgressCallback}) { - _createFileDirectory(config.path); + static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + Realm realm = await _open(config, onProgressCallback).asCancellable(cancellationToken); + return realm; + } - if (cancelationController != null) { - if (cancelationController._canceledCalled) { - return Future.value(null); - } - if (cancelationController._opration != null) { - throw RealmException("RealmCancelableOperation is already in use."); + static Future _open(Configuration config, ProgressCallback? onProgressCallback) async { + _createFileDirectory(config.path); + final realm = Realm(config); + //Initial subscriptions to be loaded here + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + if (onProgressCallback != null) { + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); + } else { + await session.waitForDownload(); } } - - RealmAsyncOpenTaskHandle realmAsyncOpenTaskHandle = realmCore.createRealmAsyncOpenTask(config); - - int progressToken = 0; - if (onProgressCallback != null) { - progressToken = realmCore.realmAsyncOpenRegisterProgressNotifier(realmAsyncOpenTaskHandle, onProgressCallback); - } - - Completer completer = Completer(); - Future realmFuture = realmCore.openRealmAsync(realmAsyncOpenTaskHandle, completer).onError((error, stackTrace) { - print(error); - return null; - }).then((handle) { - if (progressToken > 0) { - realmCore.realmAsyncOpenUnregisterProgressNotifier(realmAsyncOpenTaskHandle, progressToken); - } - cancelationController?._opration = null; - return handle != null ? Realm._(config, handle) : null; - }); - - var cancelableOperation = CancelableOperation.fromFuture(realmFuture, onCancel: () { - if (!completer.isCompleted) { - realmCore.cancelRealmAsyncOpenTask(realmAsyncOpenTaskHandle); - completer.complete(null); - } - }); - - cancelationController?._opration = cancelableOperation; - if (cancelationController?._canceledCalled == false) cancelationController?.cancel(); - - return cancelableOperation.valueOrCancellation(null); + return realm; } static RealmHandle _openRealmSync(Configuration config) { @@ -631,21 +610,3 @@ class DynamicRealm { /// * `totalBytes` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer) /// {@category Realm} typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); - -/// Represents an object containing [CancelableOperation] that allows a [Future] operations to be canceled. -/// -/// Provides a method cancel that cancels the initiated [CancelableOperation]. -/// The [CancelableOperation] is initiated internaly by the Future method that this object is passed to as an argument. -/// -/// {@category Realm} -class RealmCancelationController { - bool _canceledCalled = false; - - CancelableOperation? _opration; - - /// Cancels any running operation. - void cancel() { - _opration?.cancel(); - _canceledCalled = true; - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 8546ee298..c97c8a941 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,7 +31,7 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 - async: ^2.9.0 + cancellation_token: ^1.4.0 dev_dependencies: build_cli: ^2.2.0 diff --git a/test/realm_test.dart b/test/realm_test.dart index 0ad6da575..eee24714e 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -715,11 +715,6 @@ Future main([List? args]) async { expect(danAgain.isManaged, isFalse); // dan wasn't updated }); - test('Realm open async with local configuration throws', () async { - var config = Configuration.local([Car.schema, Person.schema]); - expect(() async => await Realm.open(config), throws("This method is only available for fully synchronized Realms")); - }); - baasTest('Realm open async', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -727,10 +722,8 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); final realm = await Realm.open(configuration); - if (realm != null) { - expect(realm.isClosed, false); - realm.close(); - } + expect(realm.isClosed, false); + realm.close(); }); baasTest('Realm open async and get progress', (appConfiguration) async { @@ -742,10 +735,8 @@ Future main([List? args]) async { final realm = await Realm.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); }); - if (realm != null) { - expect(realm.isClosed, false); - realm.close(); - } + expect(realm.isClosed, false); + realm.close(); }); baasTest('Realm open async, add data and get progress', (appConfiguration) async { @@ -772,10 +763,8 @@ Future main([List? args]) async { print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); }); var syncedRealm = await realmAsync1; - if (syncedRealm != null) { - expect(syncedRealm.isClosed, false); - syncedRealm.close(); - } + expect(syncedRealm.isClosed, false); + syncedRealm.close(); }); baasTest('Realm open async and cancel', (appConfiguration) async { @@ -784,10 +773,11 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancelationController = RealmCancelationController(); - final realm = Realm.open(configuration, cancelationController: cancelationController); - cancelationController.cancel(); - expect(await realm, isNull); + var cancellationToken = CancellationToken(); + final realm = Realm.open(configuration, cancellationToken: cancellationToken); + cancellationToken.cancel(); + expect(() async => await realm, throws()); + //expect(await realm, isNull); }); baasTest('Realm open async with the same cancelationController throws', (appConfiguration) async { @@ -796,10 +786,10 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancelationController = RealmCancelationController(); - final realm1 = Realm.open(configuration, cancelationController: cancelationController); - expect(() => Realm.open(configuration, cancelationController: cancelationController), throws("RealmCancelableOperation is already in use")); - (await realm1)!.close(); + var cancellationToken = CancellationToken(); + final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); + expect(() => Realm.open(configuration, cancellationToken: cancellationToken), throws("RealmCancelableOperation is already in use")); + (await realm1).close(); }); baasTest('Realm open async many times and cancel once', (appConfiguration) async { @@ -808,11 +798,11 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancelationController1 = RealmCancelationController(); - final realm1 = Realm.open(configuration, cancelationController: cancelationController1); - var cancelationController2 = RealmCancelationController(); - final realm2 = Realm.open(configuration, cancelationController: cancelationController2); - cancelationController1.cancel(); + var cancellationToken1 = CancellationToken(); + final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); + var cancellationToken2 = CancellationToken(); + final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); + cancellationToken1.cancel(); expect(await realm1, isNull); expect(await realm2, isNull); }); @@ -822,20 +812,20 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.anonymous()); final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); - var cancelationController1 = RealmCancelationController(); - final realm1 = Realm.open(configuration1, cancelationController: cancelationController1); + var cancellationToken1 = CancellationToken(); + final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); - var cancelationController2 = RealmCancelationController(); - final realm2 = Realm.open(configuration2, cancelationController: cancelationController2); + var cancellationToken2 = CancellationToken(); + final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); - cancelationController1.cancel(); + cancellationToken1.cancel(); expect(await realm1, isNull); var realm = await realm2; expect(realm, isNotNull); - realm!.close(); + realm.close(); }); baasTest('RealmCancelableOperation.cancel before initialization also cancel the operation', (appConfiguration) async { @@ -844,9 +834,9 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancelationController = RealmCancelationController(); - cancelationController.cancel(); - final realm = await Realm.open(configuration, cancelationController: cancelationController); + var cancellationToken = CancellationToken(); + cancellationToken.cancel(); + final realm = await Realm.open(configuration, cancellationToken: cancellationToken); expect(realm, isNull); }); @@ -856,11 +846,11 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancelationController = RealmCancelationController(); - final realm = await Realm.open(configuration, cancelationController: cancelationController); + var cancellationToken = CancellationToken(); + final realm = await Realm.open(configuration, cancellationToken: cancellationToken); expect(realm, isNotNull); - cancelationController.cancel(); - realm!.close(); + cancellationToken.cancel(); + realm.close(); }); } From d64fd5134f08de0eca1b538e4aa75c188ea2dc37 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 13 Sep 2022 17:42:44 +0300 Subject: [PATCH 028/113] Remove redundant code --- lib/src/native/realm_core.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 547f51136..128dc30bf 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1824,10 +1824,6 @@ class RealmHandle extends HandleBase { RealmHandle._unowned(Pointer pointer) : super.unowned(pointer); } -class RealmAsyncOpenTaskHandle extends HandleBase { - RealmAsyncOpenTaskHandle._(Pointer pointer) : super(pointer, 24); -} - class SchedulerHandle extends HandleBase { SchedulerHandle._(Pointer pointer) : super(pointer, 24); } From 3f13fbfd868666e241112abad9abc102f16c53f2 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 13 Sep 2022 17:58:37 +0300 Subject: [PATCH 029/113] Remove callback method --- lib/src/native/realm_bindings.dart | 34 ------------------------------ src/realm_dart_sync.cpp | 23 -------------------- src/realm_dart_sync.h | 4 +--- 3 files changed, 1 insertion(+), 60 deletions(-) diff --git a/lib/src/native/realm_bindings.dart b/lib/src/native/realm_bindings.dart index 5e658a51e..c3f9d832d 100644 --- a/lib/src/native/realm_bindings.dart +++ b/lib/src/native/realm_bindings.dart @@ -3093,32 +3093,6 @@ class RealmLibrary { ffi.Pointer Function( ffi.Pointer)>(); - void realm_dart_async_open_task_completion_callback( - ffi.Pointer userdata, - ffi.Pointer realm, - ffi.Pointer error, - ) { - return _realm_dart_async_open_task_completion_callback( - userdata, - realm, - error, - ); - } - - late final _realm_dart_async_open_task_completion_callbackPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>>( - 'realm_dart_async_open_task_completion_callback'); - late final _realm_dart_async_open_task_completion_callback = - _realm_dart_async_open_task_completion_callbackPtr.asFunction< - void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>(); - ffi.Pointer realm_dart_create_scheduler( int isolateId, int port, @@ -9716,14 +9690,6 @@ class RealmLibrary { class _SymbolAddresses { final RealmLibrary _library; _SymbolAddresses(this._library); - ffi.Pointer< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, - ffi.Pointer, - ffi.Pointer)>> - get realm_dart_async_open_task_completion_callback => - _library._realm_dart_async_open_task_completion_callbackPtr; ffi.Pointer< ffi.NativeFunction< ffi.Pointer Function(ffi.Uint64, Dart_Port)>> diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index f3b70fae3..f51961809 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -137,27 +137,4 @@ RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userda ud->scheduler->invoke([ud, state]() { (reinterpret_cast(ud->dart_callback))(ud->handle, state); }); -} - -RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t userdata, realm_thread_safe_reference_t* realm, realm_async_error_t* error) -{ - struct realm_dart_async_error : realm_async_error - { - realm_dart_async_error(const realm_async_error& error) - : realm_async_error(*error.clone()) - { - - } - }; - - std::unique_ptr error_copy; - if (error != nullptr) { - error_copy = std::make_unique(*error); - } - - - auto ud = reinterpret_cast(userdata); - ud->scheduler->invoke([ud, realm, error = std::move(error_copy)]() { - (reinterpret_cast(ud->dart_callback))(ud->handle, realm, error.get()); - }); } \ No newline at end of file diff --git a/src/realm_dart_sync.h b/src/realm_dart_sync.h index d0436aca5..14bb6eef3 100644 --- a/src/realm_dart_sync.h +++ b/src/realm_dart_sync.h @@ -34,6 +34,4 @@ RLM_API void realm_dart_sync_connection_state_changed_callback(realm_userdata_t realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state); -RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userdata_t userdata, realm_flx_sync_subscription_set_state_e state); - -RLM_API void realm_dart_async_open_task_completion_callback(realm_userdata_t userdata, realm_thread_safe_reference_t* realm, realm_async_error_t* error); \ No newline at end of file +RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userdata_t userdata, realm_flx_sync_subscription_set_state_e state); \ No newline at end of file From 69cf2fc3092dc7774b6e8e966022c08fc53a9503 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 13 Sep 2022 22:35:48 +0300 Subject: [PATCH 030/113] Create dir sync and handle CancelledException --- lib/src/realm_class.dart | 73 +++++++++++++++---- test/realm_test.dart | 153 +++++++++++++++++++-------------------- 2 files changed, 133 insertions(+), 93 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 3e291a2e0..27a5f083f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -83,7 +83,6 @@ export 'results.dart' show RealmResults, RealmResultsChanges; export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetState, MutableSubscriptionSet; export 'user.dart' show User, UserState, UserIdentity; export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress, ConnectionStateChange; -export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; /// A [Realm] instance represents a `Realm` database. /// @@ -125,37 +124,62 @@ class Realm implements Finalizable { /// [RealmCancelationController] provides method [cancel] that cancels any current running download. /// If multiple [Realm.open] operations are all in the progress for the same Realm, /// then canceling one of them will cancel all of them. - static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - Realm realm = await _open(config, onProgressCallback).asCancellable(cancellationToken); + static Future open(Configuration config, {RealmCancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + CancelledException? exception; + await _createFileDirectory(config.path); + final realm = Realm(config); + //Initial subscriptions to be loaded here + try { + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + if (onProgressCallback != null) { + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)) + .asCancellable(cancellationToken?.token); + } else { + await session.waitForDownload().asCancellable(cancellationToken?.token); + } + } + } on CancelledException catch (e) { + exception = e; + } catch (e) { + rethrow; + } + if (cancellationToken?.token.isCancelled == true && exception != null) { + throw exception; + } return realm; + + //return await _open(config, onProgressCallback).asCancellable(cancellationToken?.token); } static Future _open(Configuration config, ProgressCallback? onProgressCallback) async { - _createFileDirectory(config.path); + await _createFileDirectory(config.path); final realm = Realm(config); //Initial subscriptions to be loaded here if (config is FlexibleSyncConfiguration) { - final session = realm.syncSession; - if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); - } else { - await session.waitForDownload(); - } + // final session = realm.syncSession; + // if (onProgressCallback != null) { + // await session + // .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + // .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); + // } else { + // await session.waitForDownload(); + // } } return realm; } static RealmHandle _openRealmSync(Configuration config) { - _createFileDirectory(config.path); + Future.sync(() => _createFileDirectory(config.path)); return realmCore.openRealm(config); } - static void _createFileDirectory(String filePath) { + static Future _createFileDirectory(String filePath) async { var dir = File(filePath).parent; - if (!dir.existsSync()) { - dir.createSync(recursive: true); + if (!await dir.exists()) { + await dir.create(recursive: true); } } @@ -610,3 +634,20 @@ class DynamicRealm { /// * `totalBytes` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer) /// {@category Realm} typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); + +class RealmCancelledException implements CancelledException { + final Exception _exception; + RealmCancelledException(Exception exception) : _exception = exception; + String get message => (_exception is CancelledException) ? (_exception as CancelledException).cancellationReason ?? "" : _exception.toString(); + + @override + String? get cancellationReason => message; +} + +class RealmCancellationToken { + final token = CancellationToken(); + + void cancel() { + token.cancel(RealmCancelledException(CancelledException(cancellationReason: "Operation cancelled request"))); + } +} diff --git a/test/realm_test.dart b/test/realm_test.dart index eee24714e..6859e7191 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -773,85 +773,84 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); + var cancellationToken = RealmCancellationToken(); final realm = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - expect(() async => await realm, throws()); - //expect(await realm, isNull); - }); - - baasTest('Realm open async with the same cancelationController throws', (appConfiguration) async { - final app = App(appConfiguration); - final credentials = Credentials.anonymous(); - final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); - - var cancellationToken = CancellationToken(); - final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); - expect(() => Realm.open(configuration, cancellationToken: cancellationToken), throws("RealmCancelableOperation is already in use")); - (await realm1).close(); - }); - - baasTest('Realm open async many times and cancel once', (appConfiguration) async { - final app = App(appConfiguration); - final credentials = Credentials.anonymous(); - final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); - - var cancellationToken1 = CancellationToken(); - final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); - var cancellationToken2 = CancellationToken(); - final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); - cancellationToken1.cancel(); - expect(await realm1, isNull); - expect(await realm2, isNull); - }); - - baasTest('Realm open async to different Realms and cancel only the first', (appConfiguration) async { - final app = App(appConfiguration); - - final user1 = await app.logIn(Credentials.anonymous()); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); - var cancellationToken1 = CancellationToken(); - final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); - - final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); - var cancellationToken2 = CancellationToken(); - final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); - - cancellationToken1.cancel(); - - expect(await realm1, isNull); - var realm = await realm2; - expect(realm, isNotNull); - realm.close(); - }); - - baasTest('RealmCancelableOperation.cancel before initialization also cancel the operation', (appConfiguration) async { - final app = App(appConfiguration); - final credentials = Credentials.anonymous(); - final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); - - var cancellationToken = CancellationToken(); - cancellationToken.cancel(); - final realm = await Realm.open(configuration, cancellationToken: cancellationToken); - expect(realm, isNull); - }); - - baasTest('RealmCancelableOperation.cancel after realm is obtained', (appConfiguration) async { - final app = App(appConfiguration); - final credentials = Credentials.anonymous(); - final user = await app.logIn(credentials); - final configuration = Configuration.flexibleSync(user, [Task.schema]); - - var cancellationToken = CancellationToken(); - final realm = await Realm.open(configuration, cancellationToken: cancellationToken); - expect(realm, isNotNull); - cancellationToken.cancel(); - realm.close(); - }); + expect(() async => await realm, throws()); + }); + + // baasTest('Realm open async with the same cancelationController throws', (appConfiguration) async { + // final app = App(appConfiguration); + // final credentials = Credentials.anonymous(); + // final user = await app.logIn(credentials); + // final configuration = Configuration.flexibleSync(user, [Task.schema]); + + // var cancellationToken = CancellationToken(); + // final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); + // expect(() => Realm.open(configuration, cancellationToken: cancellationToken), throws("RealmCancelableOperation is already in use")); + // (await realm1).close(); + // }); + + // baasTest('Realm open async many times and cancel once', (appConfiguration) async { + // final app = App(appConfiguration); + // final credentials = Credentials.anonymous(); + // final user = await app.logIn(credentials); + // final configuration = Configuration.flexibleSync(user, [Task.schema]); + + // var cancellationToken1 = CancellationToken(); + // final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); + // var cancellationToken2 = CancellationToken(); + // final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); + // cancellationToken1.cancel(); + // expect(await realm1, isNull); + // expect(await realm2, isNull); + // }); + + // baasTest('Realm open async to different Realms and cancel only the first', (appConfiguration) async { + // final app = App(appConfiguration); + + // final user1 = await app.logIn(Credentials.anonymous()); + // final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); + // var cancellationToken1 = CancellationToken(); + // final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); + + // final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + // final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); + // var cancellationToken2 = CancellationToken(); + // final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); + + // cancellationToken1.cancel(); + + // expect(await realm1, isNull); + // var realm = await realm2; + // expect(realm, isNotNull); + // realm.close(); + // }); + + // baasTest('RealmCancelableOperation.cancel before initialization also cancel the operation', (appConfiguration) async { + // final app = App(appConfiguration); + // final credentials = Credentials.anonymous(); + // final user = await app.logIn(credentials); + // final configuration = Configuration.flexibleSync(user, [Task.schema]); + + // var cancellationToken = CancellationToken(); + // cancellationToken.cancel(); + // final realm = await Realm.open(configuration, cancellationToken: cancellationToken); + // expect(realm, isNull); + // }); + + // baasTest('RealmCancelableOperation.cancel after realm is obtained', (appConfiguration) async { + // final app = App(appConfiguration); + // final credentials = Credentials.anonymous(); + // final user = await app.logIn(credentials); + // final configuration = Configuration.flexibleSync(user, [Task.schema]); + + // var cancellationToken = CancellationToken(); + // final realm = await Realm.open(configuration, cancellationToken: cancellationToken); + // expect(realm, isNotNull); + // cancellationToken.cancel(); + // realm.close(); + // }); } extension _IterableEx on Iterable { From 345db63281aa77a6f1f14635c6924b89904b091a Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 14 Sep 2022 01:02:59 +0300 Subject: [PATCH 031/113] Fix wrong user realm paths --- lib/src/realm_class.dart | 2 +- test/realm_test.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 27a5f083f..38f570278 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -172,7 +172,7 @@ class Realm implements Finalizable { } static RealmHandle _openRealmSync(Configuration config) { - Future.sync(() => _createFileDirectory(config.path)); + Future.sync(() async => await _createFileDirectory(config.path)); return realmCore.openRealm(config); } diff --git a/test/realm_test.dart b/test/realm_test.dart index 6859e7191..098792029 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -743,13 +743,13 @@ Future main([List? args]) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); final realm1 = Realm(configuration1); realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); realm1.close(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); final realm2 = Realm(configuration2); realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); realm2.write(() { From 6906f2cc1c14df8e5bd875402bf436c7ad9b4ff8 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 14 Sep 2022 18:08:15 +0300 Subject: [PATCH 032/113] Close realm on cancel --- lib/src/realm_class.dart | 40 ++++++++++------------------------------ 1 file changed, 10 insertions(+), 30 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 38f570278..77badbb09 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -126,23 +126,22 @@ class Realm implements Finalizable { /// then canceling one of them will cancel all of them. static Future open(Configuration config, {RealmCancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { CancelledException? exception; - await _createFileDirectory(config.path); + _createFileDirectory(config.path); final realm = Realm(config); //Initial subscriptions to be loaded here - try { + try { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)) - .asCancellable(cancellationToken?.token); + await session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((s)=>onProgressCallback.call(s.transferredBytes, s.transferableBytes)).asCancellable(cancellationToken?.token); } else { await session.waitForDownload().asCancellable(cancellationToken?.token); } } } on CancelledException catch (e) { exception = e; + realm.close(); } catch (e) { rethrow; } @@ -150,36 +149,17 @@ class Realm implements Finalizable { throw exception; } return realm; - - //return await _open(config, onProgressCallback).asCancellable(cancellationToken?.token); - } - - static Future _open(Configuration config, ProgressCallback? onProgressCallback) async { - await _createFileDirectory(config.path); - final realm = Realm(config); - //Initial subscriptions to be loaded here - if (config is FlexibleSyncConfiguration) { - // final session = realm.syncSession; - // if (onProgressCallback != null) { - // await session - // .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - // .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); - // } else { - // await session.waitForDownload(); - // } - } - return realm; - } + } static RealmHandle _openRealmSync(Configuration config) { - Future.sync(() async => await _createFileDirectory(config.path)); + _createFileDirectory(config.path); return realmCore.openRealm(config); } - static Future _createFileDirectory(String filePath) async { + static void _createFileDirectory(String filePath) { var dir = File(filePath).parent; - if (!await dir.exists()) { - await dir.create(recursive: true); + if (!dir.existsSync()) { + dir.createSync(recursive: true); } } From f48bb515476e772a0eb4137b09099d26512d8000 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 16 Sep 2022 10:53:42 +0300 Subject: [PATCH 033/113] Implement CancellableFuture --- flutter/realm_flutter/pubspec.yaml | 1 - lib/src/realm_class.dart | 75 ++++++++++---- pubspec.yaml | 1 - test/realm_test.dart | 157 +++++++++++++++-------------- 4 files changed, 134 insertions(+), 100 deletions(-) diff --git a/flutter/realm_flutter/pubspec.yaml b/flutter/realm_flutter/pubspec.yaml index 1f9383f44..bc45ef87c 100644 --- a/flutter/realm_flutter/pubspec.yaml +++ b/flutter/realm_flutter/pubspec.yaml @@ -34,7 +34,6 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 - cancellation_token: ^1.4.0 dev_dependencies: flutter_test: diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 77badbb09..2b5d92eca 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -23,7 +23,6 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:collection/collection.dart'; -import 'package:cancellation_token/cancellation_token.dart'; import 'configuration.dart'; import 'list.dart'; @@ -124,32 +123,28 @@ class Realm implements Finalizable { /// [RealmCancelationController] provides method [cancel] that cancels any current running download. /// If multiple [Realm.open] operations are all in the progress for the same Realm, /// then canceling one of them will cancel all of them. - static Future open(Configuration config, {RealmCancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - CancelledException? exception; + static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { _createFileDirectory(config.path); final realm = Realm(config); //Initial subscriptions to be loaded here - try { + try { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - await session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((s)=>onProgressCallback.call(s.transferredBytes, s.transferableBytes)).asCancellable(cancellationToken?.token); + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)) + .asCancellable(cancellationToken, exception: CancelledException("Listening for session progress stream was canceleld")); } else { - await session.waitForDownload().asCancellable(cancellationToken?.token); + await session.waitForDownload().asCancellable(cancellationToken, exception: CancelledException("Waiting for session download was canceleld")); } } - } on CancelledException catch (e) { - exception = e; - realm.close(); } catch (e) { + realm.close(); rethrow; } - if (cancellationToken?.token.isCancelled == true && exception != null) { - throw exception; - } return realm; - } + } static RealmHandle _openRealmSync(Configuration config) { _createFileDirectory(config.path); @@ -615,19 +610,55 @@ class DynamicRealm { /// {@category Realm} typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); -class RealmCancelledException implements CancelledException { - final Exception _exception; - RealmCancelledException(Exception exception) : _exception = exception; - String get message => (_exception is CancelledException) ? (_exception as CancelledException).cancellationReason ?? "" : _exception.toString(); +/// An exception being thrown when a cancellable operation is cancelled by calling [CancellationToken.cancel]. +/// {@category Realm} +class CancelledException implements Exception { + final String message; + + CancelledException(this.message); @override - String? get cancellationReason => message; + String toString() { + return message; + } } -class RealmCancellationToken { - final token = CancellationToken(); +class CancellationToken { + bool isCanceled = false; + final _attachedCallbacks = []; + + void _attach(Function onCancel) { + _attachedCallbacks.add(onCancel); + } + + void _detach(Function onCancel) { + _attachedCallbacks.remove(onCancel); + } void cancel() { - token.cancel(RealmCancelledException(CancelledException(cancellationReason: "Operation cancelled request"))); + for (final callback in _attachedCallbacks) { + callback(); + } + isCanceled = true; + } +} + +extension CancellableFuture on Future { + Future asCancellable(CancellationToken? token, {CancelledException? exception}) async { + final completer = token == null ? null : Completer(); + + onClose() { + if (completer?.isCompleted == false) completer?.completeError(exception ?? CancelledException("CancellableFuture was canceled.")); + } + + token?._attach(onClose); + + try { + final futuresToWait = (completer?.isCompleted == false) ? [completer!.future, this] : [this]; + if (token?.isCanceled == true) onClose(); + return await Future.any(futuresToWait); + } finally { + token?._detach(onClose); + } } } diff --git a/pubspec.yaml b/pubspec.yaml index c97c8a941..bcb3ec4e5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,7 +31,6 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 - cancellation_token: ^1.4.0 dev_dependencies: build_cli: ^2.2.0 diff --git a/test/realm_test.dart b/test/realm_test.dart index 098792029..1e0067491 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -773,84 +773,89 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = RealmCancellationToken(); + var cancellationToken = CancellationToken(); final realm = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - expect(() async => await realm, throws()); - }); - - // baasTest('Realm open async with the same cancelationController throws', (appConfiguration) async { - // final app = App(appConfiguration); - // final credentials = Credentials.anonymous(); - // final user = await app.logIn(credentials); - // final configuration = Configuration.flexibleSync(user, [Task.schema]); - - // var cancellationToken = CancellationToken(); - // final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); - // expect(() => Realm.open(configuration, cancellationToken: cancellationToken), throws("RealmCancelableOperation is already in use")); - // (await realm1).close(); - // }); - - // baasTest('Realm open async many times and cancel once', (appConfiguration) async { - // final app = App(appConfiguration); - // final credentials = Credentials.anonymous(); - // final user = await app.logIn(credentials); - // final configuration = Configuration.flexibleSync(user, [Task.schema]); - - // var cancellationToken1 = CancellationToken(); - // final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); - // var cancellationToken2 = CancellationToken(); - // final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); - // cancellationToken1.cancel(); - // expect(await realm1, isNull); - // expect(await realm2, isNull); - // }); - - // baasTest('Realm open async to different Realms and cancel only the first', (appConfiguration) async { - // final app = App(appConfiguration); - - // final user1 = await app.logIn(Credentials.anonymous()); - // final configuration1 = Configuration.flexibleSync(user1, [Task.schema], path: '${Configuration.defaultStoragePath}/${user1.id}.realm'); - // var cancellationToken1 = CancellationToken(); - // final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); - - // final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - // final configuration2 = Configuration.flexibleSync(user2, [Task.schema], path: '${Configuration.defaultStoragePath}/${user2.id}.realm'); - // var cancellationToken2 = CancellationToken(); - // final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); - - // cancellationToken1.cancel(); - - // expect(await realm1, isNull); - // var realm = await realm2; - // expect(realm, isNotNull); - // realm.close(); - // }); - - // baasTest('RealmCancelableOperation.cancel before initialization also cancel the operation', (appConfiguration) async { - // final app = App(appConfiguration); - // final credentials = Credentials.anonymous(); - // final user = await app.logIn(credentials); - // final configuration = Configuration.flexibleSync(user, [Task.schema]); - - // var cancellationToken = CancellationToken(); - // cancellationToken.cancel(); - // final realm = await Realm.open(configuration, cancellationToken: cancellationToken); - // expect(realm, isNull); - // }); - - // baasTest('RealmCancelableOperation.cancel after realm is obtained', (appConfiguration) async { - // final app = App(appConfiguration); - // final credentials = Credentials.anonymous(); - // final user = await app.logIn(credentials); - // final configuration = Configuration.flexibleSync(user, [Task.schema]); - - // var cancellationToken = CancellationToken(); - // final realm = await Realm.open(configuration, cancellationToken: cancellationToken); - // expect(realm, isNotNull); - // cancellationToken.cancel(); - // realm.close(); - // }); + expect(() async => await realm, throws()); + }); + + baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancellationToken = CancellationToken(); + final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); + final realm2 = Realm.open(configuration, cancellationToken: cancellationToken); + cancellationToken.cancel(); + expect(() async => await realm1, throws()); + expect(() async => await realm2, throws()); + }); + + baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancellationToken1 = CancellationToken(); + final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); + var cancellationToken2 = CancellationToken(); + final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); + cancellationToken1.cancel(); + expect(() async => await realm1, throws()); + final openedRealm = await realm2; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); + openedRealm.close(); + }); + + baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { + final app = App(appConfiguration); + + final user1 = await app.logIn(Credentials.anonymous()); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); + var cancellationToken1 = CancellationToken(); + final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); + + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); + var cancellationToken2 = CancellationToken(); + final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); + + cancellationToken2.cancel(); + final openedRealm = await realm1; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); + openedRealm.close(); + expect(() async => await realm2, throws()); + }); + + baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancellationToken = CancellationToken(); + cancellationToken.cancel(); + expect(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throws()); + }); + + baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { + final app = App(appConfiguration); + final credentials = Credentials.anonymous(); + final user = await app.logIn(credentials); + final configuration = Configuration.flexibleSync(user, [Task.schema]); + + var cancellationToken = CancellationToken(); + final realm = await Realm.open(configuration, cancellationToken: cancellationToken); + expect(realm, isNotNull); + expect(realm.isClosed, false); + cancellationToken.cancel(); + realm.close(); + }); } extension _IterableEx on Iterable { From 96d2213278d085c90ce1d0eaecbae5df1cd56efc Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Fri, 16 Sep 2022 11:00:30 +0300 Subject: [PATCH 034/113] Update lib/src/realm_class.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- lib/src/realm_class.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 2b5d92eca..c76808457 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -124,7 +124,6 @@ class Realm implements Finalizable { /// If multiple [Realm.open] operations are all in the progress for the same Realm, /// then canceling one of them will cancel all of them. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - _createFileDirectory(config.path); final realm = Realm(config); //Initial subscriptions to be loaded here try { From 4a9172586da3dfc79f9b29181b551034f466d33d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 16 Sep 2022 11:03:08 +0300 Subject: [PATCH 035/113] Delete _createFileDirectory --- lib/src/realm_class.dart | 8 ++------ test/realm_test.dart | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 2b5d92eca..87b23b5a2 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -147,15 +147,11 @@ class Realm implements Finalizable { } static RealmHandle _openRealmSync(Configuration config) { - _createFileDirectory(config.path); - return realmCore.openRealm(config); - } - - static void _createFileDirectory(String filePath) { - var dir = File(filePath).parent; + var dir = File(config.path).parent; if (!dir.existsSync()) { dir.createSync(recursive: true); } + return realmCore.openRealm(config); } void _populateMetadata() { diff --git a/test/realm_test.dart b/test/realm_test.dart index 1e0067491..60dcc5447 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -825,6 +825,7 @@ Future main([List? args]) async { final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); cancellationToken2.cancel(); + final openedRealm = await realm1; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); From 74deaf68066a29bca7f86ff8d2a4fd82ee69cbdb Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 16 Sep 2022 12:04:17 +0300 Subject: [PATCH 036/113] Realm open async - open two different Realms --- test/realm_test.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 60dcc5447..6f5b447de 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -825,12 +825,16 @@ Future main([List? args]) async { final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); cancellationToken2.cancel(); - + final openedRealm = await realm1; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); openedRealm.close(); - expect(() async => await realm2, throws()); + try { + await realm2; + } catch (e) { + expect(e, isA()); + } }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { From 13dd44a0cf6ee72469bb2530051be1ef9e21f6b1 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 16 Sep 2022 12:27:40 +0300 Subject: [PATCH 037/113] Added tests for local realm --- test/realm_test.dart | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 6f5b447de..623608f60 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -715,7 +715,7 @@ Future main([List? args]) async { expect(danAgain.isManaged, isFalse); // dan wasn't updated }); - baasTest('Realm open async', (appConfiguration) async { + baasTest('Realm open async for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -726,6 +726,13 @@ Future main([List? args]) async { realm.close(); }); + test('Realm open async for local configuration', () async { + final configuration = Configuration.local([Car.schema]); + final realm = await Realm.open(configuration); + expect(realm.isClosed, false); + realm.close(); + }); + baasTest('Realm open async and get progress', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -767,7 +774,7 @@ Future main([List? args]) async { syncedRealm.close(); }); - baasTest('Realm open async and cancel', (appConfiguration) async { + baasTest('Realm open async and cancel for local flexibleSync', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -779,6 +786,15 @@ Future main([List? args]) async { expect(() async => await realm, throws()); }); + test('Realm open async and cancel for local configuration', () async { + final configuration = Configuration.local([Car.schema]); + var cancellationToken = CancellationToken(); + final realm = Realm.open(configuration, cancellationToken: cancellationToken); + cancellationToken.cancel(); + var openedRealm = await realm; // You can't cancel local Realm + openedRealm.close(); + }); + baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); From 5dbbed3cf52d8533fdb865cf9b39b76ace53e16a Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sat, 17 Sep 2022 00:34:11 +0300 Subject: [PATCH 038/113] Fixing test cases --- lib/src/realm_class.dart | 15 ++++++----- test/realm_test.dart | 56 ++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 911d84cd9..17625f976 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -124,22 +124,23 @@ class Realm implements Finalizable { /// If multiple [Realm.open] operations are all in the progress for the same Realm, /// then canceling one of them will cancel all of them. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - final realm = Realm(config); - //Initial subscriptions to be loaded here + Realm? realm; try { + realm = await Future.delayed(const Duration(microseconds: 1), () => Realm(config)).asCancellable(cancellationToken); + //Initial subscriptions to be loaded here if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { await session .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)) - .asCancellable(cancellationToken, exception: CancelledException("Listening for session progress stream was canceleld")); + .asCancellable(cancellationToken); } else { - await session.waitForDownload().asCancellable(cancellationToken, exception: CancelledException("Waiting for session download was canceleld")); + await session.waitForDownload().asCancellable(cancellationToken); } } } catch (e) { - realm.close(); + realm?.close(); rethrow; } return realm; @@ -639,11 +640,11 @@ class CancellationToken { } extension CancellableFuture on Future { - Future asCancellable(CancellationToken? token, {CancelledException? exception}) async { + Future asCancellable(CancellationToken? token, {String? cancelledMessage}) async { final completer = token == null ? null : Completer(); onClose() { - if (completer?.isCompleted == false) completer?.completeError(exception ?? CancelledException("CancellableFuture was canceled.")); + if (completer?.isCompleted == false) completer?.completeError(CancelledException(cancelledMessage ?? "CancellableFuture was canceled.")); } token?._attach(onClose); diff --git a/test/realm_test.dart b/test/realm_test.dart index 623608f60..d504d50d8 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -774,7 +774,7 @@ Future main([List? args]) async { syncedRealm.close(); }); - baasTest('Realm open async and cancel for local flexibleSync', (appConfiguration) async { + baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -783,16 +783,14 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - expect(() async => await realm, throws()); + await expectLater(() async => await realm, throws()); }); - test('Realm open async and cancel for local configuration', () async { + test('Realm open async and cancel before Realm.open for local configuration', () async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); - final realm = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - var openedRealm = await realm; // You can't cancel local Realm - openedRealm.close(); + await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throws()); }); baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { @@ -805,8 +803,8 @@ Future main([List? args]) async { final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); final realm2 = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - expect(() async => await realm1, throws()); - expect(() async => await realm2, throws()); + await expectLater(() async => await realm1, throws()); + await expectLater(() async => await realm2, throws()); }); baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { @@ -820,38 +818,34 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); cancellationToken1.cancel(); - expect(() async => await realm1, throws()); + await expectLater(() async => await realm1, throws()); final openedRealm = await realm2; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); openedRealm.close(); }); - baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { - final app = App(appConfiguration); + // baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { + // final app = App(appConfiguration); - final user1 = await app.logIn(Credentials.anonymous()); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); - var cancellationToken1 = CancellationToken(); - final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); + // final user1 = await app.logIn(Credentials.anonymous()); + // final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); + // var cancellationToken1 = CancellationToken(); + // final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); - final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); - var cancellationToken2 = CancellationToken(); - final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); + // final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + // final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); + // var cancellationToken2 = CancellationToken(); + // final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); - cancellationToken2.cancel(); + // cancellationToken2.cancel(); - final openedRealm = await realm1; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); - openedRealm.close(); - try { - await realm2; - } catch (e) { - expect(e, isA()); - } - }); + // final openedRealm = await realm1; + // expect(openedRealm, isNotNull); + // expect(openedRealm.isClosed, false); + + // await expectLater(() async => await realm2, throws()); + // }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { final app = App(appConfiguration); @@ -861,7 +855,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - expect(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throws()); + await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throws()); }); baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { From 6ee24359ea8899bc991be115bdc2c44e0e79ff60 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sat, 17 Sep 2022 18:57:03 +0300 Subject: [PATCH 039/113] Use throwsA in expectLater --- test/realm_test.dart | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index d504d50d8..7b912c929 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -783,14 +783,14 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() async => await realm, throws()); + await expectLater(() async => await realm, throwsA(isA())); }); test('Realm open async and cancel before Realm.open for local configuration', () async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throws()); + await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { @@ -803,8 +803,8 @@ Future main([List? args]) async { final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); final realm2 = Realm.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() async => await realm1, throws()); - await expectLater(() async => await realm2, throws()); + await expectLater(() async => await realm1, throwsA(isA())); + await expectLater(() async => await realm2, throwsA(isA())); }); baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { @@ -818,7 +818,7 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); cancellationToken1.cancel(); - await expectLater(() async => await realm1, throws()); + await expectLater(() async => await realm1, throwsA(isA())); final openedRealm = await realm2; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); @@ -844,7 +844,7 @@ Future main([List? args]) async { // expect(openedRealm, isNotNull); // expect(openedRealm.isClosed, false); - // await expectLater(() async => await realm2, throws()); + // await expectLater(() async => await realm2, throwsA(isA())); // }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { @@ -855,7 +855,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throws()); + await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { From ea4c14b0b24126c3da6208790f0fe04335be06c0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sat, 17 Sep 2022 22:14:37 +0300 Subject: [PATCH 040/113] CancellableFuture refactoring --- lib/src/realm_class.dart | 103 +++++++++++++++++++++++---------------- test/realm_test.dart | 43 +++++++++------- 2 files changed, 87 insertions(+), 59 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 17625f976..1087517b7 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -116,32 +116,31 @@ class Realm implements Finalizable { /// /// Open realm async arguments are: /// * `config`- a configuration object that describes the realm. - /// * `cancelationController` - an initialized object of [RealmCancelationController] that is used to cancel the operation. It is not mandatory. + /// * `cancellationToken` - an initialized object of [CancellationToken] that is used to cancel the operation. It is not mandatory. /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications. It is not mandatory. /// - /// The returned type is [Future] that is completed once the remote realm is fully synchronized or canceled. - /// [RealmCancelationController] provides method [cancel] that cancels any current running download. - /// If multiple [Realm.open] operations are all in the progress for the same Realm, - /// then canceling one of them will cancel all of them. + /// The returned type is [Future] that is completed once the remote realm is fully synchronized or canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - Realm? realm; - try { - realm = await Future.delayed(const Duration(microseconds: 1), () => Realm(config)).asCancellable(cancellationToken); - //Initial subscriptions to be loaded here - if (config is FlexibleSyncConfiguration) { - final session = realm.syncSession; - if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)) - .asCancellable(cancellationToken); - } else { - await session.waitForDownload().asCancellable(cancellationToken); - } + Realm realm = await CancellableFuture.fromFutureFunction(() => _open(config, cancellationToken, onProgressCallback), cancellationToken); + return realm; + } + + static Future _open(Configuration config, CancellationToken? cancellationToken, ProgressCallback? onProgressCallback) async { + Realm realm = Realm(config); + cancellationToken?.beforeCancel(() async { + realm.close(); + }); + + //Initial subscriptions to be loaded here + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + if (onProgressCallback != null) { + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); + } else { + await CancellableFuture.fromFutureFunction(() => session.waitForDownload(), cancellationToken); } - } catch (e) { - realm?.close(); - rethrow; } return realm; } @@ -619,42 +618,64 @@ class CancelledException implements Exception { } } +/// [CancellationToken] provides method [cancel] that cancels that executes [_onCancel] and [beforeCancel] callbacks. +/// It is used for canceling long Future operations. +/// {@category Realm} class CancellationToken { bool isCanceled = false; final _attachedCallbacks = []; + final _attachedBeforeCancelCallbacks = []; - void _attach(Function onCancel) { + void _onCancel(Function onCancel) { _attachedCallbacks.add(onCancel); } - void _detach(Function onCancel) { - _attachedCallbacks.remove(onCancel); + void beforeCancel(Function beforeCancel) { + _attachedBeforeCancelCallbacks.add(beforeCancel); } void cancel() { - for (final callback in _attachedCallbacks) { - callback(); + try { + for (final beforeCancelCallback in _attachedBeforeCancelCallbacks) { + beforeCancelCallback(); + } + for (final cancelCallback in _attachedCallbacks) { + cancelCallback(); + } + } finally { + isCanceled = true; + _attachedBeforeCancelCallbacks.clear(); + _attachedCallbacks.clear(); } - isCanceled = true; } } -extension CancellableFuture on Future { - Future asCancellable(CancellationToken? token, {String? cancelledMessage}) async { - final completer = token == null ? null : Completer(); +/// [CancellableFuture] provides a static method [fromFutureFunction] that builds cancellable Future +/// from Future function. +/// +/// fromFutureFunction arguments are: +/// * `futureFunction`- a function executung a Future that has to be canceled. +/// * `cancellationToken` - [CancellationToken] that is used to cancel the Future. +/// * `cancelledMessage` - am optional argument providing reasonable message of [CancelledException] thrown by the Future if the token is canceled. +/// {@category Realm} +class CancellableFuture { + static Future fromFutureFunction(Future Function() futureFunction, CancellationToken? cancellationToken, {String? cancelledMessage}) async { + if (cancellationToken != null) { + final cancelException = CancelledException(cancelledMessage ?? "CancellableFuture was canceled."); + if (cancellationToken.isCanceled) throw cancelException; - onClose() { - if (completer?.isCompleted == false) completer?.completeError(CancelledException(cancelledMessage ?? "CancellableFuture was canceled.")); - } + final completer = Completer(); - token?._attach(onClose); + cancellationToken._onCancel(() { + if (!completer.isCompleted) { + completer.completeError(cancelException); + } + }); - try { - final futuresToWait = (completer?.isCompleted == false) ? [completer!.future, this] : [this]; - if (token?.isCanceled == true) onClose(); - return await Future.any(futuresToWait); - } finally { - token?._detach(onClose); + if (!(completer.isCompleted || cancellationToken.isCanceled)) { + return await Future.any([completer.future, futureFunction()]); + } } + return await Future.any([futureFunction()]); } } diff --git a/test/realm_test.dart b/test/realm_test.dart index 7b912c929..3cbda80aa 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -815,37 +815,43 @@ Future main([List? args]) async { var cancellationToken1 = CancellationToken(); final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); + var cancellationToken2 = CancellationToken(); final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); + cancellationToken1.cancel(); - await expectLater(() async => await realm1, throwsA(isA())); + final openedRealm = await realm2; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); openedRealm.close(); + + await expectLater(() async => await realm1, throwsA(isA())); }); - // baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { - // final app = App(appConfiguration); + baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { + final app = App(appConfiguration); - // final user1 = await app.logIn(Credentials.anonymous()); - // final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); - // var cancellationToken1 = CancellationToken(); - // final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); + final user1 = await app.logIn(Credentials.anonymous()); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); + var cancellationToken1 = CancellationToken(); + final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); - // final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - // final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); - // var cancellationToken2 = CancellationToken(); - // final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); + var cancellationToken2 = CancellationToken(); + final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); - // cancellationToken2.cancel(); + cancellationToken2.cancel(); + await expectLater(() async => await realm2, throwsA(isA())); - // final openedRealm = await realm1; - // expect(openedRealm, isNotNull); - // expect(openedRealm.isClosed, false); + final openedRealm = await realm1; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); + openedRealm.close(); - // await expectLater(() async => await realm2, throwsA(isA())); - // }); + + }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { final app = App(appConfiguration); @@ -868,8 +874,9 @@ Future main([List? args]) async { final realm = await Realm.open(configuration, cancellationToken: cancellationToken); expect(realm, isNotNull); expect(realm.isClosed, false); + cancellationToken.cancel(); - realm.close(); + expect(realm.isClosed, true); }); } From 63277df90c58a9f2291b57fc25ef39e36eec5339 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 18 Sep 2022 20:57:50 +0300 Subject: [PATCH 041/113] Fixing tests --- lib/src/realm_class.dart | 2 +- test/realm_test.dart | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 1087517b7..5ef909971 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -139,7 +139,7 @@ class Realm implements Finalizable { .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); } else { - await CancellableFuture.fromFutureFunction(() => session.waitForDownload(), cancellationToken); + await session.waitForDownload(); } } return realm; diff --git a/test/realm_test.dart b/test/realm_test.dart index 3cbda80aa..dd85efcc8 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -18,6 +18,7 @@ // ignore_for_file: unused_local_variable, avoid_relative_lib_imports +import 'dart:async'; import 'dart:io'; import 'package:test/test.dart' hide test, throws; import '../lib/realm.dart'; @@ -821,12 +822,11 @@ Future main([List? args]) async { cancellationToken1.cancel(); + await expectLater(() async => await realm1, throwsA(isA())); final openedRealm = await realm2; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); openedRealm.close(); - - await expectLater(() async => await realm1, throwsA(isA())); }); baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { @@ -850,7 +850,6 @@ Future main([List? args]) async { expect(openedRealm.isClosed, false); openedRealm.close(); - }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { @@ -874,7 +873,7 @@ Future main([List? args]) async { final realm = await Realm.open(configuration, cancellationToken: cancellationToken); expect(realm, isNotNull); expect(realm.isClosed, false); - + cancellationToken.cancel(); expect(realm.isClosed, true); }); From f29891d1de73ec6c0a2d2f053e8a6eae0dc624c1 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Sun, 18 Sep 2022 23:56:54 +0300 Subject: [PATCH 042/113] Fix doc API and CHANGELOG --- CHANGELOG.md | 2 +- lib/src/realm_class.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fb2f69f9..475a02e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ ### Enhancements * Expose an API for string-based access to the objects in the `Realm`. Those are primarily intended to be used during migrations, but are available at all times for advanced use cases. [#495](https://github.com/realm/realm-dart/pull/495)) * Added `Realm.schema` property exposing the Realm's schema as passed through the Configuration or read from disk. [#495](https://github.com/realm/realm-dart/pull/495)) -* Support `Realm.open` API to asynchronously open a synchronized Realm. It will download all remote content available at the time the operation began on a background thread and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) +* Support `Realm.open` API to asynchronously open a synchronized Realm. It will download all remote content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Lifted a limitation that only allowed non-nullable primary keys. ([#458](https://github.com/realm/realm-dart/issues/458)) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5ef909971..e5353031f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -618,7 +618,7 @@ class CancelledException implements Exception { } } -/// [CancellationToken] provides method [cancel] that cancels that executes [_onCancel] and [beforeCancel] callbacks. +/// [CancellationToken] provides method [cancel] that executes [_onCancel] and [beforeCancel] callbacks. /// It is used for canceling long Future operations. /// {@category Realm} class CancellationToken { From cb8af6c2799fe49187d8a2008e879336b3918838 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 19 Sep 2022 00:33:39 +0300 Subject: [PATCH 043/113] Rename a method --- lib/src/realm_class.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index e5353031f..21de9a85c 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -127,7 +127,7 @@ class Realm implements Finalizable { static Future _open(Configuration config, CancellationToken? cancellationToken, ProgressCallback? onProgressCallback) async { Realm realm = Realm(config); - cancellationToken?.beforeCancel(() async { + cancellationToken?.onBeforeCancel(() async { realm.close(); }); @@ -630,7 +630,7 @@ class CancellationToken { _attachedCallbacks.add(onCancel); } - void beforeCancel(Function beforeCancel) { + void onBeforeCancel(Function beforeCancel) { _attachedBeforeCancelCallbacks.add(beforeCancel); } From 7ef29e90cf89b407fa5422627662dc21cf585940 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 19 Sep 2022 10:19:51 +0300 Subject: [PATCH 044/113] Token canceled in advance --- lib/src/realm_class.dart | 2 +- test/realm_test.dart | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 21de9a85c..bac6cec9f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -662,7 +662,7 @@ class CancellableFuture { static Future fromFutureFunction(Future Function() futureFunction, CancellationToken? cancellationToken, {String? cancelledMessage}) async { if (cancellationToken != null) { final cancelException = CancelledException(cancelledMessage ?? "CancellableFuture was canceled."); - if (cancellationToken.isCanceled) throw cancelException; + if (cancellationToken.isCanceled) return await Future.error(cancelException); final completer = Completer(); diff --git a/test/realm_test.dart b/test/realm_test.dart index dd85efcc8..2ba5696ba 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -821,8 +821,8 @@ Future main([List? args]) async { final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); cancellationToken1.cancel(); - await expectLater(() async => await realm1, throwsA(isA())); + final openedRealm = await realm2; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); @@ -849,7 +849,6 @@ Future main([List? args]) async { expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); openedRealm.close(); - }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { From 5536250e96937a2486b8d34fbeae8dc099ed7381 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 19 Sep 2022 14:20:33 +0300 Subject: [PATCH 045/113] Cancel with completer --- lib/src/realm_class.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index bac6cec9f..19307c692 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -662,18 +662,19 @@ class CancellableFuture { static Future fromFutureFunction(Future Function() futureFunction, CancellationToken? cancellationToken, {String? cancelledMessage}) async { if (cancellationToken != null) { final cancelException = CancelledException(cancelledMessage ?? "CancellableFuture was canceled."); - if (cancellationToken.isCanceled) return await Future.error(cancelException); - final completer = Completer(); - cancellationToken._onCancel(() { if (!completer.isCompleted) { completer.completeError(cancelException); } }); - - if (!(completer.isCompleted || cancellationToken.isCanceled)) { - return await Future.any([completer.future, futureFunction()]); + if (cancellationToken.isCanceled) { + completer.completeError(cancelException); + await completer.future; + } else { + if (!(completer.isCompleted || cancellationToken.isCanceled)) { + return await Future.any([completer.future, futureFunction()]); + } } } return await Future.any([futureFunction()]); From 3064c9b745a7661654162e0d434f9f30413add9c Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:57:33 +0300 Subject: [PATCH 046/113] Update lib/src/realm_class.dart Co-authored-by: blagoev --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index da0a1d09d..aa2ac915e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -124,7 +124,7 @@ class Realm implements Finalizable { /// * `cancellationToken` - an initialized object of [CancellationToken] that is used to cancel the operation. It is not mandatory. /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications. It is not mandatory. /// - /// The returned type is [Future] that is completed once the remote realm is fully synchronized or canceled. + /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { Realm realm = await CancellableFuture.fromFutureFunction(() => _open(config, cancellationToken, onProgressCallback), cancellationToken); return realm; From 056073304ce48605f7c785290a2913197079fdaa Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:58:05 +0300 Subject: [PATCH 047/113] Update lib/src/realm_class.dart Co-authored-by: blagoev --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index aa2ac915e..cc779e638 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -122,7 +122,7 @@ class Realm implements Finalizable { /// Open realm async arguments are: /// * `config`- a configuration object that describes the realm. /// * `cancellationToken` - an initialized object of [CancellationToken] that is used to cancel the operation. It is not mandatory. - /// * `onProgressCallback` - a function that is registered as a callback for receiving download progress notifications. It is not mandatory. + /// * `onProgressCallback` - a callback for receiving download progress notifications. /// /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { From 1f170c405d593006cf5dfe0f3c987f69d0276f0d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:58:22 +0300 Subject: [PATCH 048/113] Update lib/src/realm_class.dart Co-authored-by: blagoev --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index cc779e638..550c18c46 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -113,7 +113,7 @@ class Realm implements Finalizable { isFrozen = realmCore.isFrozen(this); } - /// A method for asynchronously obtaining and opening a [Realm]. + /// A method for asynchronously opening a [Realm]. /// /// If the configuration is [FlexibleSyncConfiguration], the realm will be downloaded and fully /// synchronized with the server prior to the completion of the returned [Future]. From c8f2288721f5a9459f13dde9f3251fe318d21cfd Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:58:46 +0300 Subject: [PATCH 049/113] Update lib/src/realm_class.dart Co-authored-by: blagoev --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 550c18c46..f6bc7e6d4 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -121,7 +121,7 @@ class Realm implements Finalizable { /// /// Open realm async arguments are: /// * `config`- a configuration object that describes the realm. - /// * `cancellationToken` - an initialized object of [CancellationToken] that is used to cancel the operation. It is not mandatory. + /// * `cancellationToken` - an optional [CancellationToken] used to cancel the operation. /// * `onProgressCallback` - a callback for receiving download progress notifications. /// /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. From 5cea4d7a81ca1306b14b0e1db8ddb2d72b504d19 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Tue, 20 Sep 2022 09:59:32 +0300 Subject: [PATCH 050/113] Update lib/src/realm_class.dart Co-authored-by: blagoev --- lib/src/realm_class.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index f6bc7e6d4..5caceae16 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -119,7 +119,6 @@ class Realm implements Finalizable { /// synchronized with the server prior to the completion of the returned [Future]. /// Otherwise this method will throw an exception. /// - /// Open realm async arguments are: /// * `config`- a configuration object that describes the realm. /// * `cancellationToken` - an optional [CancellationToken] used to cancel the operation. /// * `onProgressCallback` - a callback for receiving download progress notifications. From 2d10b89e353eadb2debe0c944b2f08f75c9f56ed Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 20 Sep 2022 10:17:40 +0300 Subject: [PATCH 051/113] Code review changes --- CHANGELOG.md | 2 +- lib/src/realm_class.dart | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ba42ace8..0021a6fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,7 +26,7 @@ ### Enhancements * Expose an API for string-based access to the objects in the `Realm`. Those are primarily intended to be used during migrations, but are available at all times for advanced use cases. [#495](https://github.com/realm/realm-dart/pull/495)) * Added `Realm.schema` property exposing the Realm's schema as passed through the Configuration or read from disk. [#495](https://github.com/realm/realm-dart/pull/495)) -* Support `Realm.open` API to asynchronously open a synchronized Realm. It will download all remote content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) +* Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Lifted a limitation that only allowed non-nullable primary keys. ([#458](https://github.com/realm/realm-dart/issues/458)) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5caceae16..251ba1854 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -117,13 +117,14 @@ class Realm implements Finalizable { /// /// If the configuration is [FlexibleSyncConfiguration], the realm will be downloaded and fully /// synchronized with the server prior to the completion of the returned [Future]. - /// Otherwise this method will throw an exception. + /// This method could be called also for opening a local [Realm]. /// /// * `config`- a configuration object that describes the realm. /// * `cancellationToken` - an optional [CancellationToken] used to cancel the operation. - /// * `onProgressCallback` - a callback for receiving download progress notifications. + /// * `onProgressCallback` - a callback for receiving download progress notifications for synced [Realm]s. /// /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. + /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { Realm realm = await CancellableFuture.fromFutureFunction(() => _open(config, cancellationToken, onProgressCallback), cancellationToken); return realm; @@ -663,7 +664,7 @@ typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); /// An exception being thrown when a cancellable operation is cancelled by calling [CancellationToken.cancel]. /// {@category Realm} -class CancelledException implements Exception { +class CancelledException implements RealmException { final String message; CancelledException(this.message); From 1395503738ed01089229d95e7e610b3316e49ef0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 20 Sep 2022 12:56:07 +0300 Subject: [PATCH 052/113] Do waitForDownload always when FLXS --- lib/src/realm_class.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 251ba1854..44e18f84d 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -143,9 +143,8 @@ class Realm implements Finalizable { await session .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); - } else { - await session.waitForDownload(); } + await session.waitForDownload(); } return realm; } From 44e181a7b3ae345074cd5095dc0f9e5e65c7e286 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 20 Sep 2022 15:41:43 +0300 Subject: [PATCH 053/113] Update Changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ada840c..3105a021c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Enhancements * Added support for "frozen objects" - these are objects, queries, lists, or Realms that have been "frozen" at a specific version. All frozen objects can be accessed and queried as normal, but attempting to mutate them or add change listeners will throw an exception. `Realm`, `RealmObject`, `RealmList`, and `RealmResults` now have a method `freeze()` which returns an immutable version of the object, as well as an `isFrozen` property which can be used to check whether an object is frozen. (Issue [#56](https://github.com/realm/realm-dart/issues/56)) +* Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Allow null arguments on query. ([#871](https://github.com/realm/realm-dart/issues/871)) @@ -32,7 +33,6 @@ ### Enhancements * Expose an API for string-based access to the objects in the `Realm`. Those are primarily intended to be used during migrations, but are available at all times for advanced use cases. [#495](https://github.com/realm/realm-dart/pull/495)) * Added `Realm.schema` property exposing the Realm's schema as passed through the Configuration or read from disk. [#495](https://github.com/realm/realm-dart/pull/495)) -* Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Lifted a limitation that only allowed non-nullable primary keys. ([#458](https://github.com/realm/realm-dart/issues/458)) From 165791f3cb053fc3d090cea5ab00510ebd0b904c Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 20 Sep 2022 19:57:40 +0300 Subject: [PATCH 054/113] Realm open async if realm already exists --- lib/src/realm_class.dart | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 44e18f84d..ff7d5f7d7 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -105,10 +105,15 @@ class Realm implements Finalizable { /// and will not update when writes are made to the database. late final bool isFrozen; + /// Returns true if the [Realm] is opened for the first time and the realm file is created. + late final bool _openedFirstTime; + /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle]) : _handle = handle ?? _openRealmSync(config) { + Realm._(this.config, [RealmHandle? handle]) + : _openedFirstTime = !File(config.path).existsSync(), + _handle = handle ?? _openRealmSync(config) { _populateMetadata(); isFrozen = realmCore.isFrozen(this); } @@ -136,7 +141,6 @@ class Realm implements Finalizable { realm.close(); }); - //Initial subscriptions to be loaded here if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { @@ -144,7 +148,11 @@ class Realm implements Finalizable { .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); } - await session.waitForDownload(); + if (realm._openedFirstTime) { + await session.waitForDownload(); + } else { + await realm.subscriptions.waitForSynchronization(); + } } return realm; } From f31c62c920ed1605879807d0438adf221991db92 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 20 Sep 2022 20:31:38 +0300 Subject: [PATCH 055/113] Implement RealmA.open helper method for tests --- test/realm_test.dart | 37 +++++++++++++++---------------------- test/test.dart | 13 +++++++++++++ 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 9d0d55297..3121ab2a6 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -804,23 +804,21 @@ Future main([List? args]) async { expect(realm.isClosed, true); expect(frozen.isClosed, true); }); - + baasTest('Realm open async for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = await Realm.open(configuration); + final realm = await RealmA.open(configuration); expect(realm.isClosed, false); - realm.close(); }); test('Realm open async for local configuration', () async { final configuration = Configuration.local([Car.schema]); - final realm = await Realm.open(configuration); + final realm = await RealmA.open(configuration); expect(realm.isClosed, false); - realm.close(); }); baasTest('Realm open async and get progress', (appConfiguration) async { @@ -829,11 +827,10 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = await Realm.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { + final realm = await RealmA.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); }); expect(realm.isClosed, false); - realm.close(); }); baasTest('Realm open async, add data and get progress', (appConfiguration) async { @@ -854,14 +851,12 @@ Future main([List? args]) async { realm2.add(Task(ObjectId())); } }); - realm2.close(); - final realmAsync1 = Realm.open(configuration1, onProgressCallback: (transferredBytes, totalBytes) { + final realmAsync1 = RealmA.open(configuration1, onProgressCallback: (transferredBytes, totalBytes) { print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); }); var syncedRealm = await realmAsync1; expect(syncedRealm.isClosed, false); - syncedRealm.close(); }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { @@ -871,7 +866,7 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm = Realm.open(configuration, cancellationToken: cancellationToken); + final realm = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); await expectLater(() async => await realm, throwsA(isA())); }); @@ -880,7 +875,7 @@ Future main([List? args]) async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(() async => await RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { @@ -890,8 +885,8 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm1 = Realm.open(configuration, cancellationToken: cancellationToken); - final realm2 = Realm.open(configuration, cancellationToken: cancellationToken); + final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); + final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); await expectLater(() async => await realm1, throwsA(isA())); await expectLater(() async => await realm2, throwsA(isA())); @@ -904,10 +899,10 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken1 = CancellationToken(); - final realm1 = Realm.open(configuration, cancellationToken: cancellationToken1); + final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken1); var cancellationToken2 = CancellationToken(); - final realm2 = Realm.open(configuration, cancellationToken: cancellationToken2); + final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken2); cancellationToken1.cancel(); await expectLater(() async => await realm1, throwsA(isA())); @@ -915,7 +910,6 @@ Future main([List? args]) async { final openedRealm = await realm2; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); - openedRealm.close(); }); baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { @@ -924,12 +918,12 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.anonymous()); final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); var cancellationToken1 = CancellationToken(); - final realm1 = Realm.open(configuration1, cancellationToken: cancellationToken1); + final realm1 = RealmA.open(configuration1, cancellationToken: cancellationToken1); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); - final realm2 = Realm.open(configuration2, cancellationToken: cancellationToken2); + final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); cancellationToken2.cancel(); await expectLater(() async => await realm2, throwsA(isA())); @@ -937,7 +931,6 @@ Future main([List? args]) async { final openedRealm = await realm1; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); - openedRealm.close(); }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { @@ -948,7 +941,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() async => await Realm.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(() async => await RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { @@ -958,7 +951,7 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm = await Realm.open(configuration, cancellationToken: cancellationToken); + final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); expect(realm, isNotNull); expect(realm.isClosed, false); diff --git a/test/test.dart b/test/test.dart index 354dc8dd3..8580d5b24 100644 --- a/test/test.dart +++ b/test/test.dart @@ -535,3 +535,16 @@ Future _printPlatformInfo() async { print('Current PID $pid; OS $os, $pointerSize bit, CPU ${cpu ?? 'unknown'}'); } + +class RealmA { + static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + { + if (config is FlexibleSyncConfiguration) { + config.sessionStopPolicy = SessionStopPolicy.immediately; + } + final realm = await Realm.open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback); + _openRealms.add(realm); + return realm; + } + } +} From 4d16c8c2ecd21ab73780381d19ab7e49964866c1 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 20 Sep 2022 21:07:07 +0300 Subject: [PATCH 056/113] Fix merge --- lib/src/realm_class.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 53a073128..5404e939b 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -112,8 +112,8 @@ class Realm implements Finalizable { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) - : _openedFirstTime = !File(config.path).existsSync(), + Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) + : _openedFirstTime = !File(config.path).existsSync(), _handle = handle ?? _openRealmSync(config) { _populateMetadata(); isFrozen = realmCore.isFrozen(this); @@ -762,3 +762,4 @@ class CancellableFuture { } return await Future.any([futureFunction()]); } +} From 521b5fcb19eb398304e3846644ab916499cb7231 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 21 Sep 2022 00:13:50 +0300 Subject: [PATCH 057/113] Add waiting for download --- lib/src/realm_class.dart | 7 +++--- test/configuration_test.dart | 6 ++--- test/realm_test.dart | 45 ++++++++++++++++++------------------ 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5404e939b..5546f3ac1 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -149,10 +149,9 @@ class Realm implements Finalizable { .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); } - if (realm._openedFirstTime) { - await session.waitForDownload(); - } else { - await realm.subscriptions.waitForSynchronization(); + await session.waitForDownload(); + if (!realm._openedFirstTime) { + await session.waitForUpload(); } } return realm; diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 0a67b7b46..6a871cfd4 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -506,7 +506,7 @@ Future main([List? args]) async { path.basename('my-custom-realm-name.realm'), ); final config = Configuration.flexibleSync(user, [Event.schema], path: customPath); - var realm = Realm(config); + var realm = getRealm(config); }); baasTest('Configuration.disconnectedSync', (appConfig) async { @@ -518,7 +518,7 @@ Future main([List? args]) async { final schema = [Task.schema]; final flexibleSyncConfig = Configuration.flexibleSync(user, schema, path: realmPath); - final realm = Realm(flexibleSyncConfig); + final realm = getRealm(flexibleSyncConfig); final oid = ObjectId(); realm.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realm.query(r'_id == $0', [oid])); @@ -527,7 +527,7 @@ Future main([List? args]) async { realm.close(); final disconnectedSyncConfig = Configuration.disconnectedSync(schema, path: realmPath); - final disconnectedRealm = Realm(disconnectedSyncConfig); + final disconnectedRealm = getRealm(disconnectedSyncConfig); expect(disconnectedRealm.find(oid), isNotNull); }); } diff --git a/test/realm_test.dart b/test/realm_test.dart index 99b416654..4a7f01f55 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -807,6 +807,24 @@ Future main([List? args]) async { expect(frozen.isClosed, true); }); + test('Subtype of supported type (TZDateTime)', () { + final realm = getRealm(Configuration.local([When.schema])); + tz.initializeTimeZones(); + + final cph = tz.getLocation('Europe/Copenhagen'); + final now = tz.TZDateTime.now(cph); + final when = newWhen(now); + + realm.write(() => realm.add(when)); + + final stored = realm.all().first.dateTime; + + expect(stored, now); + expect(stored.timeZone, now.timeZone); + expect(stored.location, now.location); + expect(stored.location.name, 'Europe/Copenhagen'); + }); + baasTest('Realm open async for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -840,19 +858,21 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); - final realm1 = Realm(configuration1); + final realm1 = getRealm(configuration1); realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); - realm1.close(); + await realm1.subscriptions.waitForSynchronization(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); - final realm2 = Realm(configuration2); + final realm2 = getRealm(configuration2); realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); realm2.write(() { for (var i = 0; i < 100; i++) { realm2.add(Task(ObjectId())); } }); + await realm2.subscriptions.waitForSynchronization(); + await realm2.syncSession.waitForUpload(); final realmAsync1 = RealmA.open(configuration1, onProgressCallback: (transferredBytes, totalBytes) { print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); @@ -959,26 +979,7 @@ Future main([List? args]) async { cancellationToken.cancel(); expect(realm.isClosed, true); - }); - - test('Subtype of supported type (TZDateTime)', () { - final realm = getRealm(Configuration.local([When.schema])); - tz.initializeTimeZones(); - - final cph = tz.getLocation('Europe/Copenhagen'); - final now = tz.TZDateTime.now(cph); - final when = newWhen(now); - - realm.write(() => realm.add(when)); - - final stored = realm.all().first.dateTime; - - expect(stored, now); - expect(stored.timeZone, now.timeZone); - expect(stored.location, now.location); - expect(stored.location.name, 'Europe/Copenhagen'); }); - } extension on When { From 432dc78c91bd97dfd7ec368c3ce5fea062fd1959 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Wed, 21 Sep 2022 09:21:01 +0300 Subject: [PATCH 058/113] Update lib/src/realm_class.dart --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5546f3ac1..5dfe1a855 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -743,7 +743,7 @@ class CancellationToken { class CancellableFuture { static Future fromFutureFunction(Future Function() futureFunction, CancellationToken? cancellationToken, {String? cancelledMessage}) async { if (cancellationToken != null) { - final cancelException = CancelledException(cancelledMessage ?? "CancellableFuture was canceled."); + final cancelException = CancelledException(cancelledMessage ?? "Canceled operation."); final completer = Completer(); cancellationToken._onCancel(() { if (!completer.isCompleted) { From d80f18c37a0bfa2d70e59f46bc677fde0894d265 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 23 Sep 2022 15:29:36 +0300 Subject: [PATCH 059/113] Code review changes --- lib/src/realm_class.dart | 35 +++++++++-------------------------- test/realm_test.dart | 8 ++++---- 2 files changed, 13 insertions(+), 30 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5dfe1a855..fd1ed9efd 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -106,15 +106,11 @@ class Realm implements Finalizable { /// and will not update when writes are made to the database. late final bool isFrozen; - /// Returns true if the [Realm] is opened for the first time and the realm file is created. - late final bool _openedFirstTime; - /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) - : _openedFirstTime = !File(config.path).existsSync(), - _handle = handle ?? _openRealmSync(config) { + : _handle = handle ?? _openRealmSync(config) { _populateMetadata(); isFrozen = realmCore.isFrozen(this); } @@ -132,13 +128,12 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - Realm realm = await CancellableFuture.fromFutureFunction(() => _open(config, cancellationToken, onProgressCallback), cancellationToken); - return realm; + return await CancellableFuture.fromFutureFunction(() => _open(config, cancellationToken, onProgressCallback), cancellationToken); } static Future _open(Configuration config, CancellationToken? cancellationToken, ProgressCallback? onProgressCallback) async { Realm realm = Realm(config); - cancellationToken?.onBeforeCancel(() async { + cancellationToken?.onBeforeCancel(() { realm.close(); }); @@ -147,12 +142,9 @@ class Realm implements Finalizable { if (onProgressCallback != null) { await session .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((s) => onProgressCallback.call(s.transferredBytes, s.transferableBytes)); + .forEach((syncProgress) => onProgressCallback.call(syncProgress)); } await session.waitForDownload(); - if (!realm._openedFirstTime) { - await session.waitForUpload(); - } } return realm; } @@ -681,23 +673,14 @@ class MigrationRealm extends DynamicRealm { /// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. /// This is the registered callback onProgressCallback to receive progress notifications while the download is in progress. /// -/// It is called with the following arguments: -/// * `transferredBytes` - the current number of bytes already transferred -/// * `totalBytes` - the total number of transferable bytes (the number of bytes already transferred plus the number of bytes pending transfer) +/// * syncProgress - an object of [SyncProgress] that contains `transferredBytes` and `transferableBytes`. /// {@category Realm} -typedef ProgressCallback = void Function(int transferredBytes, int totalBytes); +typedef ProgressCallback = void Function(SyncProgress syncProgress); -/// An exception being thrown when a cancellable operation is cancelled by calling [CancellationToken.cancel]. +/// An exception being thrown when a operation is cancelled by calling [CancellationToken.cancel]. /// {@category Realm} -class CancelledException implements RealmException { - final String message; - - CancelledException(this.message); - - @override - String toString() { - return message; - } +class CancelledException extends RealmException { + CancelledException(super.message); } /// [CancellationToken] provides method [cancel] that executes [_onCancel] and [beforeCancel] callbacks. diff --git a/test/realm_test.dart b/test/realm_test.dart index 4a7f01f55..ffc3287c2 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -847,8 +847,8 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = await RealmA.open(configuration, onProgressCallback: (transferredBytes, totalBytes) { - print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); + final realm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); expect(realm.isClosed, false); }); @@ -874,8 +874,8 @@ Future main([List? args]) async { await realm2.subscriptions.waitForSynchronization(); await realm2.syncSession.waitForUpload(); - final realmAsync1 = RealmA.open(configuration1, onProgressCallback: (transferredBytes, totalBytes) { - print("transferredBytes: $transferredBytes, totalBytes:$totalBytes"); + final realmAsync1 = RealmA.open(configuration1, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); var syncedRealm = await realmAsync1; expect(syncedRealm.isClosed, false); From b11a6ccedb5776e80e9df5bf5e34610459441d87 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 4 Oct 2022 16:49:12 +0300 Subject: [PATCH 060/113] completeError completer in sessionWaitForDownload --- lib/src/native/realm_core.dart | 16 ++++---- lib/src/realm_class.dart | 51 ++++++++++++++++--------- lib/src/session.dart | 2 +- test/realm_test.dart | 70 +++++++++++++++++++++------------- 4 files changed, 88 insertions(+), 51 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index e60c73d6f..95e8911ba 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1756,8 +1756,9 @@ class _RealmCore { return completer.future; } - Future sessionWaitForDownload(Session session) { + Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { final completer = Completer(); + cancellationToken?.onBeforeCancel(() => completer.cancel(cancellationToken)); final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, @@ -1767,12 +1768,13 @@ class _RealmCore { static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { final completer = userdata as Completer; - - if (errorCode != nullptr) { - // Throw RealmException instead of RealmError to be recoverable by the user. - completer.completeError(RealmException(errorCode.toSyncError().toString())); - } else { - completer.complete(); + if (!completer.isCompleted) { + if (errorCode != nullptr) { + // Throw RealmException instead of RealmError to be recoverable by the user. + completer.completeError(RealmException(errorCode.toSyncError().toString())); + } else { + completer.complete(); + } } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index f6951c165..84b2054c6 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -109,8 +109,7 @@ class Realm implements Finalizable { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) - : _handle = handle ?? _openRealmSync(config) { + Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) : _handle = handle ?? _openRealmSync(config) { _populateMetadata(); isFrozen = realmCore.isFrozen(this); } @@ -140,11 +139,18 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((syncProgress) => onProgressCallback.call(syncProgress)); + final subscription = session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .listen((syncProgress) => onProgressCallback.call(syncProgress)); + + cancellationToken?.onBeforeCancel(() { + subscription.cancel(); + }); } - await session.waitForDownload(); + await session.waitForDownload(cancellationToken: cancellationToken); } return realm; } @@ -676,7 +682,7 @@ typedef ProgressCallback = void Function(SyncProgress syncProgress); /// An exception being thrown when a operation is cancelled by calling [CancellationToken.cancel]. /// {@category Realm} class CancelledException extends RealmException { - CancelledException(super.message); + CancelledException(super.message); } /// [CancellationToken] provides method [cancel] that executes [_onCancel] and [beforeCancel] callbacks. @@ -687,6 +693,11 @@ class CancellationToken { final _attachedCallbacks = []; final _attachedBeforeCancelCallbacks = []; + CancellationToken({String cancellationExceptionMessage = "Canceled operation."}) : _cancellationExceptionMessage = cancellationExceptionMessage; + + final String _cancellationExceptionMessage; + CancelledException get cancelException => CancelledException(_cancellationExceptionMessage); + void _onCancel(Function onCancel) { _attachedCallbacks.add(onCancel); } @@ -711,6 +722,15 @@ class CancellationToken { } } +/// @nodoc +extension CancelableCompleter on Completer { + void cancel(CancellationToken cancellationToken) { + if (!isCompleted) { + completeError(cancellationToken.cancelException); + } + } +} + /// [CancellableFuture] provides a static method [fromFutureFunction] that builds cancellable Future /// from Future function. /// @@ -722,22 +742,19 @@ class CancellationToken { class CancellableFuture { static Future fromFutureFunction(Future Function() futureFunction, CancellationToken? cancellationToken, {String? cancelledMessage}) async { if (cancellationToken != null) { - final cancelException = CancelledException(cancelledMessage ?? "Canceled operation."); final completer = Completer(); cancellationToken._onCancel(() { - if (!completer.isCompleted) { - completer.completeError(cancelException); - } + completer.cancel(cancellationToken); }); if (cancellationToken.isCanceled) { - completer.completeError(cancelException); - await completer.future; + completer.cancel(cancellationToken); + return await completer.future; } else { - if (!(completer.isCompleted || cancellationToken.isCanceled)) { - return await Future.any([completer.future, futureFunction()]); - } + return await Future.any([completer.future, futureFunction()]); } } - return await Future.any([futureFunction()]); + else { + return await futureFunction(); + } } } diff --git a/lib/src/session.dart b/lib/src/session.dart index 08f9284e1..50ec003ee 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -61,7 +61,7 @@ class Session implements Finalizable { Future waitForUpload() => realmCore.sessionWaitForUpload(this); /// Waits for the [Session] to finish all pending downloads. - Future waitForDownload() => realmCore.sessionWaitForDownload(this); + Future waitForDownload({CancellationToken? cancellationToken}) => realmCore.sessionWaitForDownload(this, cancellationToken: cancellationToken); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { diff --git a/test/realm_test.dart b/test/realm_test.dart index ffc3287c2..136288fc3 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -856,31 +856,28 @@ Future main([List? args]) async { baasTest('Realm open async, add data and get progress', (appConfiguration) async { final app = App(appConfiguration); - final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); - final realm1 = getRealm(configuration1); - realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); - await realm1.subscriptions.waitForSynchronization(); - - final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); - final realm2 = getRealm(configuration2); - realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); - realm2.write(() { - for (var i = 0; i < 100; i++) { - realm2.add(Task(ObjectId())); - } - }); - await realm2.subscriptions.waitForSynchronization(); - await realm2.syncSession.waitForUpload(); + FlexibleSyncConfiguration configuration = await addDataToAtlas(app); - final realmAsync1 = RealmA.open(configuration1, onProgressCallback: (syncProgress) { + final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); - var syncedRealm = await realmAsync1; + var syncedRealm = await realm; expect(syncedRealm.isClosed, false); }); + // baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { + // final app = App(appConfiguration); + + // FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + + // var cancellationToken = CancellationToken(); + // final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { + // print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + // }); + // cancellationToken.cancel(); + // await expectLater(() => realm, throwsA(isA())); + // }); + baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -890,14 +887,14 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() async => await realm, throwsA(isA())); + await expectLater(() => realm, throwsA(isA())); }); test('Realm open async and cancel before Realm.open for local configuration', () async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() async => await RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(() => RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { @@ -910,8 +907,8 @@ Future main([List? args]) async { final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() async => await realm1, throwsA(isA())); - await expectLater(() async => await realm2, throwsA(isA())); + await expectLater(() => realm1, throwsA(isA())); + await expectLater(() => realm2, throwsA(isA())); }); baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { @@ -927,7 +924,7 @@ Future main([List? args]) async { final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken2); cancellationToken1.cancel(); - await expectLater(() async => await realm1, throwsA(isA())); + await expectLater(() => realm1, throwsA(isA())); final openedRealm = await realm2; expect(openedRealm, isNotNull); @@ -948,7 +945,7 @@ Future main([List? args]) async { final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); cancellationToken2.cancel(); - await expectLater(() async => await realm2, throwsA(isA())); + await expectLater(() => realm2, throwsA(isA())); final openedRealm = await realm1; expect(openedRealm, isNotNull); @@ -963,7 +960,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() async => await RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(() => RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { @@ -982,6 +979,27 @@ Future main([List? args]) async { }); } +Future addDataToAtlas(App app) async { + final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); + final realm1 = getRealm(configuration1); + realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); + await realm1.subscriptions.waitForSynchronization(); + + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); + final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); + final realm2 = getRealm(configuration2); + realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + realm2.write(() { + for (var i = 0; i < 100; i++) { + realm2.add(Task(ObjectId())); + } + }); + await realm2.subscriptions.waitForSynchronization(); + await realm2.syncSession.waitForUpload(); + return configuration1; +} + extension on When { tz.TZDateTime get dateTime => tz.TZDateTime.from(dateTimeUtc, tz.getLocation(locationName)); } From 81563d0acc83bcc0eff5b11b4fb2022a20baa5d6 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 4 Oct 2022 22:29:53 +0300 Subject: [PATCH 061/113] get progress and cancel --- lib/src/native/realm_core.dart | 8 +++++--- lib/src/realm_class.dart | 13 ++++++------- lib/src/session.dart | 3 ++- test/realm_test.dart | 32 +++++++++++++++++--------------- 4 files changed, 30 insertions(+), 26 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 95e8911ba..4bd39acbf 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1756,14 +1756,16 @@ class _RealmCore { return completer.future; } - Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { + Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) async { final completer = Completer(); - cancellationToken?.onBeforeCancel(() => completer.cancel(cancellationToken)); + cancellationToken?.onBeforeCancel(() { + completer.cancel(cancellationToken); + }); final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - return completer.future; + return await completer.future; } static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 84b2054c6..50b9e9922 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -139,16 +139,16 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - final subscription = session + StreamSubscription? subscription; + cancellationToken?.onBeforeCancel(() { + subscription?.cancel(); + }); + subscription = session .getProgressStream( ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork, ) .listen((syncProgress) => onProgressCallback.call(syncProgress)); - - cancellationToken?.onBeforeCancel(() { - subscription.cancel(); - }); } await session.waitForDownload(cancellationToken: cancellationToken); } @@ -752,8 +752,7 @@ class CancellableFuture { } else { return await Future.any([completer.future, futureFunction()]); } - } - else { + } else { return await futureFunction(); } } diff --git a/lib/src/session.dart b/lib/src/session.dart index 50ec003ee..8b3e8ed6e 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -61,7 +61,8 @@ class Session implements Finalizable { Future waitForUpload() => realmCore.sessionWaitForUpload(this); /// Waits for the [Session] to finish all pending downloads. - Future waitForDownload({CancellationToken? cancellationToken}) => realmCore.sessionWaitForDownload(this, cancellationToken: cancellationToken); + Future waitForDownload({CancellationToken? cancellationToken}) async => + await realmCore.sessionWaitForDownload(this, cancellationToken: cancellationToken); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { diff --git a/test/realm_test.dart b/test/realm_test.dart index 136288fc3..6c94472e3 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -865,18 +865,18 @@ Future main([List? args]) async { expect(syncedRealm.isClosed, false); }); - // baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { - // final app = App(appConfiguration); + baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { + final app = App(appConfiguration); - // FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + FlexibleSyncConfiguration configuration = await addDataToAtlas(app); - // var cancellationToken = CancellationToken(); - // final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { - // print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - // }); - // cancellationToken.cancel(); - // await expectLater(() => realm, throwsA(isA())); - // }); + var cancellationToken = CancellationToken(); + final realm = RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + }); + cancellationToken.cancel(); + await expectLater(() async => await realm, throwsA(isA())); + }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); @@ -981,14 +981,14 @@ Future main([List? args]) async { Future addDataToAtlas(App app) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); - final realm1 = getRealm(configuration1); + final config1 = Configuration.flexibleSync(user1, [Task.schema]); + final realm1 = getRealm(config1); realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); await realm1.subscriptions.waitForSynchronization(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); - final realm2 = getRealm(configuration2); + final config2 = Configuration.flexibleSync(user2, [Task.schema]); + final realm2 = getRealm(config2); realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); realm2.write(() { for (var i = 0; i < 100; i++) { @@ -997,7 +997,9 @@ Future addDataToAtlas(App app) async { }); await realm2.subscriptions.waitForSynchronization(); await realm2.syncSession.waitForUpload(); - return configuration1; + realm1.close(); + realm2.close(); + return config1; } extension on When { From 512fbf0f0dfbcc0e2a50ecf9d79f9d773f5a46fa Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 4 Oct 2022 23:45:28 +0300 Subject: [PATCH 062/113] Completer extension makeCancellable --- lib/src/native/realm_core.dart | 8 +++----- lib/src/realm_class.dart | 10 ++++++++++ lib/src/session.dart | 3 +-- test/realm_test.dart | 21 +++++++++++++-------- 4 files changed, 27 insertions(+), 15 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 4bd39acbf..40d7ab124 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1756,16 +1756,14 @@ class _RealmCore { return completer.future; } - Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) async { + Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { final completer = Completer(); - cancellationToken?.onBeforeCancel(() { - completer.cancel(cancellationToken); - }); + if (cancellationToken != null) completer.makeCancellable(cancellationToken); final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); - return await completer.future; + return completer.future; } static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 50b9e9922..23c0958da 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -724,6 +724,16 @@ class CancellationToken { /// @nodoc extension CancelableCompleter on Completer { + void makeCancellable(CancellationToken cancellationToken) { + if (cancellationToken.isCanceled == true) { + cancel(cancellationToken); + } else { + cancellationToken.onBeforeCancel(() { + cancel(cancellationToken); + }); + } + } + void cancel(CancellationToken cancellationToken) { if (!isCompleted) { completeError(cancellationToken.cancelException); diff --git a/lib/src/session.dart b/lib/src/session.dart index 8b3e8ed6e..50ec003ee 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -61,8 +61,7 @@ class Session implements Finalizable { Future waitForUpload() => realmCore.sessionWaitForUpload(this); /// Waits for the [Session] to finish all pending downloads. - Future waitForDownload({CancellationToken? cancellationToken}) async => - await realmCore.sessionWaitForDownload(this, cancellationToken: cancellationToken); + Future waitForDownload({CancellationToken? cancellationToken}) => realmCore.sessionWaitForDownload(this, cancellationToken: cancellationToken); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { diff --git a/test/realm_test.dart b/test/realm_test.dart index 6c94472e3..599a495c3 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -871,11 +871,15 @@ Future main([List? args]) async { FlexibleSyncConfiguration configuration = await addDataToAtlas(app); var cancellationToken = CancellationToken(); - final realm = RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - }); - cancellationToken.cancel(); - await expectLater(() async => await realm, throwsA(isA())); + + Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken.cancel()); + + await expectLater( + RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + }), + throwsA(isA()), + ); }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { @@ -944,12 +948,13 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); - cancellationToken2.cancel(); - await expectLater(() => realm2, throwsA(isA())); - + final openedRealm = await realm1; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); + cancellationToken2.cancel(); + await expectLater(() => realm2, throwsA(isA())); + }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { From ed5b0cae32a80e122e2774fa868ae4244404c966 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 00:41:41 +0300 Subject: [PATCH 063/113] wait for getProgressStream --- lib/src/native/realm_core.dart | 3 +-- lib/src/realm_class.dart | 42 +++++++++++++++++++--------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 59cb8d0a7..5e4dc5c6d 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1778,8 +1778,7 @@ class _RealmCore { } Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { - final completer = Completer(); - if (cancellationToken != null) completer.makeCancellable(cancellationToken); + final completer = Completer().makeCancellable(cancellationToken); final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d70699ca1..8afa0b9af 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -140,16 +140,19 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - StreamSubscription? subscription; - cancellationToken?.onBeforeCancel(() { - subscription?.cancel(); - }); - subscription = session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) - .listen((syncProgress) => onProgressCallback.call(syncProgress)); + final completer = Completer().makeCancellable(cancellationToken); + session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( + (syncProgress) { + onProgressCallback.call(syncProgress); + if (cancellationToken?.isCanceled == true) { + completer.cancel(cancellationToken); + } + }, + cancelOnError: true, + ).onDone( + () => completer.complete(), + ); + await completer.future; } await session.waitForDownload(cancellationToken: cancellationToken); } @@ -747,18 +750,21 @@ class CancellationToken { /// @nodoc extension CancelableCompleter on Completer { - void makeCancellable(CancellationToken cancellationToken) { - if (cancellationToken.isCanceled == true) { - cancel(cancellationToken); - } else { - cancellationToken.onBeforeCancel(() { + Completer makeCancellable(CancellationToken? cancellationToken) { + if (cancellationToken != null) { + if (cancellationToken.isCanceled == true) { cancel(cancellationToken); - }); + } else { + cancellationToken.onBeforeCancel(() { + cancel(cancellationToken); + }); + } } + return this; } - void cancel(CancellationToken cancellationToken) { - if (!isCompleted) { + void cancel(CancellationToken? cancellationToken) { + if (cancellationToken != null && !isCompleted) { completeError(cancellationToken.cancelException); } } From 53c2f6f4a4b0d5cc255a74e3d14a1b4e7045d072 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 09:08:40 +0300 Subject: [PATCH 064/113] don't wait for getProgressStream, cancel on error --- lib/src/realm_class.dart | 57 ++++++++++------------------------ test/realm_test.dart | 67 ++++++++++++++++++++-------------------- 2 files changed, 51 insertions(+), 73 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 8afa0b9af..f02ce6ff2 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -128,31 +128,26 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - return await CancellableFuture.fromFutureFunction(() => _open(config, cancellationToken, onProgressCallback), cancellationToken); + return await CancellableFuture.fromFutureFunction( + () => _open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback), cancellationToken); } - static Future _open(Configuration config, CancellationToken? cancellationToken, ProgressCallback? onProgressCallback) async { + static Future _open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { Realm realm = Realm(config); - cancellationToken?.onBeforeCancel(() { + cancellationToken?.onCancel(() { realm.close(); }); if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - final completer = Completer().makeCancellable(cancellationToken); - session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( - (syncProgress) { - onProgressCallback.call(syncProgress); - if (cancellationToken?.isCanceled == true) { - completer.cancel(cancellationToken); - } - }, - cancelOnError: true, - ).onDone( - () => completer.complete(), + final subscription = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( + (syncProgress) => onProgressCallback.call(syncProgress), + cancelOnError: true, + ); + subscription.onError( + (Object error) => subscription.cancel(), ); - await completer.future; } await session.waitForDownload(cancellationToken: cancellationToken); } @@ -711,51 +706,42 @@ class CancelledException extends RealmException { CancelledException(super.message); } -/// [CancellationToken] provides method [cancel] that executes [_onCancel] and [beforeCancel] callbacks. +/// [CancellationToken] provides method [cancel] that executes all the attached [onCancel] callbacks. /// It is used for canceling long Future operations. /// {@category Realm} class CancellationToken { bool isCanceled = false; final _attachedCallbacks = []; - final _attachedBeforeCancelCallbacks = []; CancellationToken({String cancellationExceptionMessage = "Canceled operation."}) : _cancellationExceptionMessage = cancellationExceptionMessage; final String _cancellationExceptionMessage; CancelledException get cancelException => CancelledException(_cancellationExceptionMessage); - void _onCancel(Function onCancel) { + void onCancel(Function onCancel) { _attachedCallbacks.add(onCancel); } - void onBeforeCancel(Function beforeCancel) { - _attachedBeforeCancelCallbacks.add(beforeCancel); - } - void cancel() { try { - for (final beforeCancelCallback in _attachedBeforeCancelCallbacks) { - beforeCancelCallback(); - } for (final cancelCallback in _attachedCallbacks) { cancelCallback(); } } finally { isCanceled = true; - _attachedBeforeCancelCallbacks.clear(); _attachedCallbacks.clear(); } } } /// @nodoc -extension CancelableCompleter on Completer { +extension $CancelableCompleter on Completer { Completer makeCancellable(CancellationToken? cancellationToken) { if (cancellationToken != null) { if (cancellationToken.isCanceled == true) { cancel(cancellationToken); } else { - cancellationToken.onBeforeCancel(() { + cancellationToken.onCancel(() { cancel(cancellationToken); }); } @@ -776,21 +762,12 @@ extension CancelableCompleter on Completer { /// fromFutureFunction arguments are: /// * `futureFunction`- a function executung a Future that has to be canceled. /// * `cancellationToken` - [CancellationToken] that is used to cancel the Future. -/// * `cancelledMessage` - am optional argument providing reasonable message of [CancelledException] thrown by the Future if the token is canceled. /// {@category Realm} class CancellableFuture { - static Future fromFutureFunction(Future Function() futureFunction, CancellationToken? cancellationToken, {String? cancelledMessage}) async { + static Future fromFutureFunction(Future Function() futureFunction, [CancellationToken? cancellationToken]) async { if (cancellationToken != null) { - final completer = Completer(); - cancellationToken._onCancel(() { - completer.cancel(cancellationToken); - }); - if (cancellationToken.isCanceled) { - completer.cancel(cancellationToken); - return await completer.future; - } else { - return await Future.any([completer.future, futureFunction()]); - } + final completer = Completer().makeCancellable(cancellationToken); + return await Future.any([completer.future, futureFunction()]); } else { return await futureFunction(); } diff --git a/test/realm_test.dart b/test/realm_test.dart index 9308b8a20..76bb7e9fa 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -815,36 +815,7 @@ Future main([List? args]) async { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); expect(realm.isClosed, false); - }); - - baasTest('Realm open async, add data and get progress', (appConfiguration) async { - final app = App(appConfiguration); - - FlexibleSyncConfiguration configuration = await addDataToAtlas(app); - - final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - }); - var syncedRealm = await realm; - expect(syncedRealm.isClosed, false); - }); - - baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { - final app = App(appConfiguration); - - FlexibleSyncConfiguration configuration = await addDataToAtlas(app); - - var cancellationToken = CancellationToken(); - - Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken.cancel()); - - await expectLater( - RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - }), - throwsA(isA()), - ); - }); + }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); @@ -910,15 +881,15 @@ Future main([List? args]) async { final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); + Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken2.cancel()); final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); + await expectLater(() => realm2, throwsA(isA())); - final openedRealm = await realm1; expect(openedRealm, isNotNull); expect(openedRealm.isClosed, false); - cancellationToken2.cancel(); - await expectLater(() => realm2, throwsA(isA())); + }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { @@ -940,12 +911,42 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); + expect(realm, isNotNull); expect(realm.isClosed, false); cancellationToken.cancel(); expect(realm.isClosed, true); }); + + baasTest('Realm open async, add data and get progress', (appConfiguration) async { + final app = App(appConfiguration); + + FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + + final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + }); + var syncedRealm = await realm; + expect(syncedRealm.isClosed, false); + }); + + baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { + final app = App(appConfiguration); + + FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + + var cancellationToken = CancellationToken(); + + Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken.cancel()); + + await expectLater( + RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + }), + throwsA(isA()), + ); + }); } Future addDataToAtlas(App app) async { From 1a85bbd28ddc50fe9606295404755382bdca2cbf Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 09:43:40 +0300 Subject: [PATCH 065/113] Cancel in future delayed --- test/realm_test.dart | 40 +++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 76bb7e9fa..85726c8e2 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -815,7 +815,7 @@ Future main([List? args]) async { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); expect(realm.isClosed, false); - }); + }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { final app = App(appConfiguration); @@ -862,12 +862,19 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken2); - cancellationToken1.cancel(); - await expectLater(() => realm1, throwsA(isA())); + final validFuture = Future.delayed(Duration(milliseconds: 200), () async { + final openedRealm = await realm2; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); + }); - final openedRealm = await realm2; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); + final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { + cancellationToken1.cancel(); + await expectLater(() => realm1, throwsA(isA())); + }); + + await validFuture; + await canceledFuture; }); baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { @@ -881,15 +888,22 @@ Future main([List? args]) async { final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); - Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken2.cancel()); + final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); - await expectLater(() => realm2, throwsA(isA())); - final openedRealm = await realm1; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); + final validFuture = Future.delayed(Duration(milliseconds: 200), () async { + final openedRealm = await realm1; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); + }); - + final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { + cancellationToken2.cancel(); + await expectLater(() => realm2, throwsA(isA())); + }); + + await validFuture; + await canceledFuture; }); baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { @@ -911,7 +925,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); - + expect(realm, isNotNull); expect(realm.isClosed, false); From a60f5469cc31bbbbcdeb7653286765d83b1ca846 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 14:36:04 +0300 Subject: [PATCH 066/113] Using cancellatin_token package --- flutter/realm_flutter/pubspec.yaml | 1 + lib/src/native/realm_core.dart | 2 +- lib/src/realm_class.dart | 124 ++++++++++------------------- pubspec.yaml | 1 + test/realm_test.dart | 14 ++-- 5 files changed, 53 insertions(+), 89 deletions(-) diff --git a/flutter/realm_flutter/pubspec.yaml b/flutter/realm_flutter/pubspec.yaml index bc45ef87c..fb2c1d3b9 100644 --- a/flutter/realm_flutter/pubspec.yaml +++ b/flutter/realm_flutter/pubspec.yaml @@ -34,6 +34,7 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 + cancellation_token: ^1.5.0 dev_dependencies: flutter_test: diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 5e4dc5c6d..ea2b2a02f 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1778,7 +1778,7 @@ class _RealmCore { } Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { - final completer = Completer().makeCancellable(cancellationToken); + final completer = CancellableCompleter(cancellationToken); final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index f02ce6ff2..1ee78b3be 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -23,6 +23,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:collection/collection.dart'; +import 'package:cancellation_token/cancellation_token.dart'; import 'configuration.dart'; import 'list.dart'; @@ -33,6 +34,7 @@ import 'scheduler.dart'; import 'subscription.dart'; import 'session.dart'; +export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException, CancellableCompleter, CancellableFuture; export 'package:realm_common/realm_common.dart' show Ignored, @@ -128,32 +130,31 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - return await CancellableFuture.fromFutureFunction( - () => _open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback), cancellationToken); - } - - static Future _open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - Realm realm = Realm(config); - cancellationToken?.onCancel(() { - realm.close(); - }); + Realm realm = (() => Realm(config)).asCancellable(cancellationToken, onCancel: (result) => result?.close()); if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; - if (onProgressCallback != null) { - final subscription = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); - subscription.onError( - (Object error) => subscription.cancel(), - ); - } + _attachSyncProgressNotifications(session, onProgressCallback); await session.waitForDownload(cancellationToken: cancellationToken); } + await Future.delayed(Duration(seconds: 3)).asCancellable(cancellationToken); return realm; } + static void _attachSyncProgressNotifications(Session session, ProgressCallback? onProgressCallback) { + if (onProgressCallback != null) { + final subscription = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( + (syncProgress) => onProgressCallback.call(syncProgress), + cancelOnError: true, + ); + subscription.onError( + (Object error) { + Realm.logger.log(Level.INFO, error.toString()); + }, + ); + } + } + static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { @@ -700,76 +701,37 @@ class MigrationRealm extends DynamicRealm { /// {@category Realm} typedef ProgressCallback = void Function(SyncProgress syncProgress); -/// An exception being thrown when a operation is cancelled by calling [CancellationToken.cancel]. -/// {@category Realm} -class CancelledException extends RealmException { - CancelledException(super.message); -} - -/// [CancellationToken] provides method [cancel] that executes all the attached [onCancel] callbacks. -/// It is used for canceling long Future operations. -/// {@category Realm} -class CancellationToken { - bool isCanceled = false; - final _attachedCallbacks = []; - - CancellationToken({String cancellationExceptionMessage = "Canceled operation."}) : _cancellationExceptionMessage = cancellationExceptionMessage; - - final String _cancellationExceptionMessage; - CancelledException get cancelException => CancelledException(_cancellationExceptionMessage); - - void onCancel(Function onCancel) { - _attachedCallbacks.add(onCancel); - } - - void cancel() { - try { - for (final cancelCallback in _attachedCallbacks) { - cancelCallback(); - } - } finally { - isCanceled = true; - _attachedCallbacks.clear(); - } +extension CancellableFunc on T Function() { + T asCancellable(CancellationToken? cancellationToken, {required Function(T? result) onCancel}) { + final cancellableFunction = CancellableFunction( + cancellationToken, + function: () => this(), + whenCancel: (result, ex, [stackTrace]) => onCancel(result), + ); + return cancellableFunction.result; } } -/// @nodoc -extension $CancelableCompleter on Completer { - Completer makeCancellable(CancellationToken? cancellationToken) { - if (cancellationToken != null) { - if (cancellationToken.isCanceled == true) { - cancel(cancellationToken); - } else { - cancellationToken.onCancel(() { - cancel(cancellationToken); - }); - } +class CancellableFunction with Cancellable { + T result; + CancellableFunction(CancellationToken? cancellationToken, {required this.function, required this.whenCancel}) + : _cancellationToken = cancellationToken, + result = function() { + if (!maybeAttach(_cancellationToken)) { + throw _cancellationToken!.exception; } - return this; } - void cancel(CancellationToken? cancellationToken) { - if (cancellationToken != null && !isCompleted) { - completeError(cancellationToken.cancelException); - } + final CancellationToken? _cancellationToken; + final Function(T? result, Exception cancelException, [StackTrace? stackTrace]) whenCancel; + final T Function() function; + + void complete() { + _cancellationToken?.detach(this); } -} -/// [CancellableFuture] provides a static method [fromFutureFunction] that builds cancellable Future -/// from Future function. -/// -/// fromFutureFunction arguments are: -/// * `futureFunction`- a function executung a Future that has to be canceled. -/// * `cancellationToken` - [CancellationToken] that is used to cancel the Future. -/// {@category Realm} -class CancellableFuture { - static Future fromFutureFunction(Future Function() futureFunction, [CancellationToken? cancellationToken]) async { - if (cancellationToken != null) { - final completer = Completer().makeCancellable(cancellationToken); - return await Future.any([completer.future, futureFunction()]); - } else { - return await futureFunction(); - } + @override + onCancel(Exception cancelException, [StackTrace? stackTrace]) { + whenCancel(result, cancelException, stackTrace); } } diff --git a/pubspec.yaml b/pubspec.yaml index 9ac5e1335..1f1c9cc7d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: tar: ^0.5.4 build_runner: ^2.1.0 http: ^0.13.4 + cancellation_token: ^1.5.0 dev_dependencies: build_cli: ^2.2.0 diff --git a/test/realm_test.dart b/test/realm_test.dart index 85726c8e2..cdf126227 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -826,14 +826,14 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() => realm, throwsA(isA())); + await expectLater(realm, throwsA(isA())); }); test('Realm open async and cancel before Realm.open for local configuration', () async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() => RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { @@ -846,8 +846,8 @@ Future main([List? args]) async { final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() => realm1, throwsA(isA())); - await expectLater(() => realm2, throwsA(isA())); + await expectLater(realm1, throwsA(isA())); + await expectLater(realm2, throwsA(isA())); }); baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { @@ -870,7 +870,7 @@ Future main([List? args]) async { final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { cancellationToken1.cancel(); - await expectLater(() => realm1, throwsA(isA())); + await expectLater(realm1, throwsA(isA())); }); await validFuture; @@ -899,7 +899,7 @@ Future main([List? args]) async { final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { cancellationToken2.cancel(); - await expectLater(() => realm2, throwsA(isA())); + await expectLater(realm2, throwsA(isA())); }); await validFuture; @@ -914,7 +914,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() => RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { From 0e3cff167cb4acbd53fe9ee384b97a5ab2d41bfd Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 14:46:17 +0300 Subject: [PATCH 067/113] Hide CancellableFunction --- lib/src/realm_class.dart | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 1ee78b3be..07514379c 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -127,7 +127,7 @@ class Realm implements Finalizable { /// * `cancellationToken` - an optional [CancellationToken] used to cancel the operation. /// * `onProgressCallback` - a callback for receiving download progress notifications for synced [Realm]s. /// - /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. + /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with a [CancelledException] if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { Realm realm = (() => Realm(config)).asCancellable(cancellationToken, onCancel: (result) => result?.close()); @@ -701,9 +701,9 @@ class MigrationRealm extends DynamicRealm { /// {@category Realm} typedef ProgressCallback = void Function(SyncProgress syncProgress); -extension CancellableFunc on T Function() { +extension $CancellableFunc on T Function() { T asCancellable(CancellationToken? cancellationToken, {required Function(T? result) onCancel}) { - final cancellableFunction = CancellableFunction( + final cancellableFunction = _CancellableFunction( cancellationToken, function: () => this(), whenCancel: (result, ex, [stackTrace]) => onCancel(result), @@ -712,9 +712,9 @@ extension CancellableFunc on T Function() { } } -class CancellableFunction with Cancellable { +class _CancellableFunction with Cancellable { T result; - CancellableFunction(CancellationToken? cancellationToken, {required this.function, required this.whenCancel}) + _CancellableFunction(CancellationToken? cancellationToken, {required this.function, required this.whenCancel}) : _cancellationToken = cancellationToken, result = function() { if (!maybeAttach(_cancellationToken)) { From 013a0f1a3a02dad9dd99a5814dc2356d590ea117 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 14:48:10 +0300 Subject: [PATCH 068/113] Dart formatting --- lib/src/realm_class.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 07514379c..276b9f7b4 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -130,7 +130,10 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with a [CancelledException] if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - Realm realm = (() => Realm(config)).asCancellable(cancellationToken, onCancel: (result) => result?.close()); + Realm realm = (() => Realm(config)).asCancellable( + cancellationToken, + onCancel: (result) => result?.close(), + ); if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; @@ -143,7 +146,12 @@ class Realm implements Finalizable { static void _attachSyncProgressNotifications(Session session, ProgressCallback? onProgressCallback) { if (onProgressCallback != null) { - final subscription = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( + final subscription = session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .listen( (syncProgress) => onProgressCallback.call(syncProgress), cancelOnError: true, ); From 313760523d4d50a86dd2ce3bf8dd2b311269efe8 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 15:00:30 +0300 Subject: [PATCH 069/113] Remove code for testing futures --- lib/src/realm_class.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 276b9f7b4..afecee3fe 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -140,7 +140,6 @@ class Realm implements Finalizable { _attachSyncProgressNotifications(session, onProgressCallback); await session.waitForDownload(cancellationToken: cancellationToken); } - await Future.delayed(Duration(seconds: 3)).asCancellable(cancellationToken); return realm; } From 2e67ff289e4a4e847aaee9cc7b08a67c907734be Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 18:10:28 +0300 Subject: [PATCH 070/113] wait for progress for a realm with subscriptions --- lib/src/realm_class.dart | 29 ++++++------------- test/realm_test.dart | 62 ++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 42 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index afecee3fe..94643f7e1 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -137,31 +137,20 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; - _attachSyncProgressNotifications(session, onProgressCallback); + if (onProgressCallback != null) { + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((syncProgress) => onProgressCallback.call(syncProgress)) + .asCancellable(cancellationToken) + .onError((error, stackTrace) { + if (error is CancelledException) Realm.logger.log(Level.WARNING, error); + }); + } await session.waitForDownload(cancellationToken: cancellationToken); } return realm; } - static void _attachSyncProgressNotifications(Session session, ProgressCallback? onProgressCallback) { - if (onProgressCallback != null) { - final subscription = session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) - .listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); - subscription.onError( - (Object error) { - Realm.logger.log(Level.INFO, error.toString()); - }, - ); - } - } - static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { diff --git a/test/realm_test.dart b/test/realm_test.dart index cdf126227..d9f53d300 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -811,10 +811,15 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { + int printCount = 0; + + var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + printCount++; }); - expect(realm.isClosed, false); + + expect(syncedRealm.isClosed, false); + expect(printCount, isNot(0)); }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { @@ -933,47 +938,61 @@ Future main([List? args]) async { expect(realm.isClosed, true); }); - baasTest('Realm open async, add data and get progress', (appConfiguration) async { + baasTest('Realm open async with added data', (appConfiguration) async { + final app = App(appConfiguration); + final config = await addDataToAtlas(app); + var syncedRealm = await RealmA.open(config); + expect(syncedRealm.isClosed, false); + }); + + baasTest('Realm open async with added data and get progress', (appConfiguration) async { final app = App(appConfiguration); - FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + final config = await addDataToAtlas(app); - final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { + int printCount = 0; + int transferredBytes = 0; + var syncedRealm = await RealmA.open(config, onProgressCallback: (syncProgress) { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + printCount++; + transferredBytes = syncProgress.transferredBytes; }); - var syncedRealm = await realm; + expect(syncedRealm.isClosed, false); + expect(printCount, isNot(0)); + expect(transferredBytes > 2000, isTrue); }); - baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { + baasTest('Realm open async with added data, get progres and swith cancel', (appConfiguration) async { final app = App(appConfiguration); - FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + final config = await addDataToAtlas(app); var cancellationToken = CancellationToken(); - - Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken.cancel()); - - await expectLater( - RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - }), - throwsA(isA()), - ); + final realm = RealmA.open(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + }); + cancellationToken.cancel(); + await expectLater(realm, throwsA(isA())); }); } -Future addDataToAtlas(App app) async { +Future addDataToAtlas(App app) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config1 = Configuration.flexibleSync(user1, [Task.schema]); final realm1 = getRealm(config1); - realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); - await realm1.subscriptions.waitForSynchronization(); + if (realm1.subscriptions.find(realm1.all()) == null) { + realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); + await realm1.subscriptions.waitForSynchronization(); + } final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config2 = Configuration.flexibleSync(user2, [Task.schema]); final realm2 = getRealm(config2); - realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + if (realm2.subscriptions.find(realm2.all()) == null) { + realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + await realm2.subscriptions.waitForSynchronization(); + } realm2.write(() { for (var i = 0; i < 100; i++) { realm2.add(Task(ObjectId())); @@ -981,7 +1000,6 @@ Future addDataToAtlas(App app) async { }); await realm2.subscriptions.waitForSynchronization(); await realm2.syncSession.waitForUpload(); - realm1.close(); realm2.close(); return config1; } From 0609d62213d89266384186a01490879cb55561fb Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 5 Oct 2022 19:35:57 +0300 Subject: [PATCH 071/113] Close the realm on cancel only after all the operation have completed --- lib/src/realm_class.dart | 32 +++++++++++++++++++------------- test/realm_test.dart | 2 +- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 94643f7e1..8095a7803 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -130,23 +130,29 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with a [CancelledException] if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + final completer = Completer(); + Realm realm = (() => Realm(config)).asCancellable( cancellationToken, - onCancel: (result) => result?.close(), + onCancel: (result) => completer.future.then((value) { + result?.close(); + }), ); - - if (config is FlexibleSyncConfiguration) { - final session = realm.syncSession; - if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((syncProgress) => onProgressCallback.call(syncProgress)) - .asCancellable(cancellationToken) - .onError((error, stackTrace) { - if (error is CancelledException) Realm.logger.log(Level.WARNING, error); - }); + try { + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + if (onProgressCallback != null) { + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((syncProgress) => onProgressCallback.call(syncProgress)) + .asCancellable(cancellationToken) + .onError((error, stackTrace) => Realm.logger.log(Level.WARNING, error)); + } + await session.waitForDownload(cancellationToken: cancellationToken); } - await session.waitForDownload(cancellationToken: cancellationToken); + } on CancelledException { + completer.complete(); + rethrow; } return realm; } diff --git a/test/realm_test.dart b/test/realm_test.dart index d9f53d300..bbc987bac 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -935,7 +935,7 @@ Future main([List? args]) async { expect(realm.isClosed, false); cancellationToken.cancel(); - expect(realm.isClosed, true); + expect(realm.isClosed, false); }); baasTest('Realm open async with added data', (appConfiguration) async { From daf8e79d0c568acffeffd97f8acf3ab59f0b0afc Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 00:38:10 +0300 Subject: [PATCH 072/113] await for progress notifications --- lib/src/realm_class.dart | 11 +++--- test/realm_test.dart | 76 +++++++++++++++++++++++++--------------- 2 files changed, 51 insertions(+), 36 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index f02ce6ff2..a3e6b9e9e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -141,13 +141,10 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - final subscription = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); - subscription.onError( - (Object error) => subscription.cancel(), - ); + await session + .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) + .forEach((syncProgress) => onProgressCallback.call(syncProgress)) + .onError((error, stackTrace) => Realm.logger.log(Level.WARNING, error)); } await session.waitForDownload(cancellationToken: cancellationToken); } diff --git a/test/realm_test.dart b/test/realm_test.dart index 85726c8e2..d9f53d300 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -811,10 +811,15 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { + int printCount = 0; + + var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + printCount++; }); - expect(realm.isClosed, false); + + expect(syncedRealm.isClosed, false); + expect(printCount, isNot(0)); }); baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { @@ -826,14 +831,14 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() => realm, throwsA(isA())); + await expectLater(realm, throwsA(isA())); }); test('Realm open async and cancel before Realm.open for local configuration', () async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() => RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { @@ -846,8 +851,8 @@ Future main([List? args]) async { final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); cancellationToken.cancel(); - await expectLater(() => realm1, throwsA(isA())); - await expectLater(() => realm2, throwsA(isA())); + await expectLater(realm1, throwsA(isA())); + await expectLater(realm2, throwsA(isA())); }); baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { @@ -870,7 +875,7 @@ Future main([List? args]) async { final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { cancellationToken1.cancel(); - await expectLater(() => realm1, throwsA(isA())); + await expectLater(realm1, throwsA(isA())); }); await validFuture; @@ -899,7 +904,7 @@ Future main([List? args]) async { final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { cancellationToken2.cancel(); - await expectLater(() => realm2, throwsA(isA())); + await expectLater(realm2, throwsA(isA())); }); await validFuture; @@ -914,7 +919,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(() => RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { @@ -933,47 +938,61 @@ Future main([List? args]) async { expect(realm.isClosed, true); }); - baasTest('Realm open async, add data and get progress', (appConfiguration) async { + baasTest('Realm open async with added data', (appConfiguration) async { + final app = App(appConfiguration); + final config = await addDataToAtlas(app); + var syncedRealm = await RealmA.open(config); + expect(syncedRealm.isClosed, false); + }); + + baasTest('Realm open async with added data and get progress', (appConfiguration) async { final app = App(appConfiguration); - FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + final config = await addDataToAtlas(app); - final realm = RealmA.open(configuration, onProgressCallback: (syncProgress) { + int printCount = 0; + int transferredBytes = 0; + var syncedRealm = await RealmA.open(config, onProgressCallback: (syncProgress) { print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + printCount++; + transferredBytes = syncProgress.transferredBytes; }); - var syncedRealm = await realm; + expect(syncedRealm.isClosed, false); + expect(printCount, isNot(0)); + expect(transferredBytes > 2000, isTrue); }); - baasTest('Realm open async with cancel, add data and get progress', (appConfiguration) async { + baasTest('Realm open async with added data, get progres and swith cancel', (appConfiguration) async { final app = App(appConfiguration); - FlexibleSyncConfiguration configuration = await addDataToAtlas(app); + final config = await addDataToAtlas(app); var cancellationToken = CancellationToken(); - - Future.delayed(Duration(milliseconds: 3)).then((_) => cancellationToken.cancel()); - - await expectLater( - RealmA.open(configuration, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - }), - throwsA(isA()), - ); + final realm = RealmA.open(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + }); + cancellationToken.cancel(); + await expectLater(realm, throwsA(isA())); }); } -Future addDataToAtlas(App app) async { +Future addDataToAtlas(App app) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config1 = Configuration.flexibleSync(user1, [Task.schema]); final realm1 = getRealm(config1); - realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); - await realm1.subscriptions.waitForSynchronization(); + if (realm1.subscriptions.find(realm1.all()) == null) { + realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); + await realm1.subscriptions.waitForSynchronization(); + } final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config2 = Configuration.flexibleSync(user2, [Task.schema]); final realm2 = getRealm(config2); - realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + if (realm2.subscriptions.find(realm2.all()) == null) { + realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + await realm2.subscriptions.waitForSynchronization(); + } realm2.write(() { for (var i = 0; i < 100; i++) { realm2.add(Task(ObjectId())); @@ -981,7 +1000,6 @@ Future addDataToAtlas(App app) async { }); await realm2.subscriptions.waitForSynchronization(); await realm2.syncSession.waitForUpload(); - realm1.close(); realm2.close(); return config1; } From 46825d48d85c063bdc2cc74f32fb9397de333c5a Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 10:41:15 +0300 Subject: [PATCH 073/113] Avoid "Cannot access a Session that belongs to a closed Realm" --- lib/src/native/realm_core.dart | 10 +++--- lib/src/realm_class.dart | 57 ++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index ea2b2a02f..388e0a7c9 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1779,10 +1779,12 @@ class _RealmCore { Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { final completer = CancellableCompleter(cancellationToken); - final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, - userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); + if (!completer.isCancelled) { + final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); + final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); + _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, + userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); + } return completer.future; } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 8095a7803..d420afbb4 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -130,33 +130,48 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with a [CancelledException] if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - final completer = Completer(); - - Realm realm = (() => Realm(config)).asCancellable( - cancellationToken, - onCancel: (result) => completer.future.then((value) { - result?.close(); - }), - ); + Realm realm = (() => Realm(config)).asCancellable(cancellationToken); try { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((syncProgress) => onProgressCallback.call(syncProgress)) - .asCancellable(cancellationToken) - .onError((error, stackTrace) => Realm.logger.log(Level.WARNING, error)); + await _syncProgressNotifier(session, onProgressCallback, cancellationToken); } await session.waitForDownload(cancellationToken: cancellationToken); } - } on CancelledException { - completer.complete(); + } catch (error) { + // Make sure that the realm is closed on error + // after all the other waiting operations were completed or canceled. + // This is at the end in order to avoid the exceptions + // for acessing handles that belong to a closed Realm. + realm.close(); rethrow; } return realm; } + static Future _syncProgressNotifier(Session session, ProgressCallback onProgressCallback, [CancellationToken? cancellationToken]) async { + StreamSubscription? subscription; + try { + final progressCompleter = CancellableCompleter(cancellationToken); + if (!progressCompleter.isCancelled) { + final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); + subscription = progressStream.listen( + (syncProgress) => onProgressCallback.call(syncProgress), + onDone: () => progressCompleter.complete(), + onError: (Object error) => progressCompleter.completeError(error), + cancelOnError: true, + ); + } + await progressCompleter.future; + } catch (error) { + // Make sure that StreamSubscription is cancelled on error before to continue. + // This will prevent recieving exceptions for acessing handles that belong to a closed Realm + // in case the Realm is closed before this `subsription.cancel` to complete. + await subscription?.cancel(); + } + } + static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { @@ -704,11 +719,11 @@ class MigrationRealm extends DynamicRealm { typedef ProgressCallback = void Function(SyncProgress syncProgress); extension $CancellableFunc on T Function() { - T asCancellable(CancellationToken? cancellationToken, {required Function(T? result) onCancel}) { + T asCancellable(CancellationToken? cancellationToken, {Function(T? result)? onCancel}) { final cancellableFunction = _CancellableFunction( cancellationToken, function: () => this(), - whenCancel: (result, ex, [stackTrace]) => onCancel(result), + whenCancel: (result, ex, [stackTrace]) => onCancel != null ? onCancel(result) : null, ); return cancellableFunction.result; } @@ -716,7 +731,7 @@ extension $CancellableFunc on T Function() { class _CancellableFunction with Cancellable { T result; - _CancellableFunction(CancellationToken? cancellationToken, {required this.function, required this.whenCancel}) + _CancellableFunction(CancellationToken? cancellationToken, {required this.function, this.whenCancel}) : _cancellationToken = cancellationToken, result = function() { if (!maybeAttach(_cancellationToken)) { @@ -725,7 +740,7 @@ class _CancellableFunction with Cancellable { } final CancellationToken? _cancellationToken; - final Function(T? result, Exception cancelException, [StackTrace? stackTrace]) whenCancel; + final Function(T? result, Exception cancelException, [StackTrace? stackTrace])? whenCancel; final T Function() function; void complete() { @@ -734,6 +749,8 @@ class _CancellableFunction with Cancellable { @override onCancel(Exception cancelException, [StackTrace? stackTrace]) { - whenCancel(result, cancelException, stackTrace); + if (whenCancel != null) { + whenCancel!(result, cancelException, stackTrace); + } } } From e9449a61aa5fbffa9b1846e73ab45b2939d53732 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 10:47:33 +0300 Subject: [PATCH 074/113] Avoid "Cannot access a Session that belongs to a closed Realm" --- lib/src/realm_class.dart | 47 ++++++++++++++++++++++++++++++---------- test/realm_test.dart | 2 +- 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index a3e6b9e9e..2aa5bdce7 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -134,23 +134,46 @@ class Realm implements Finalizable { static Future _open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { Realm realm = Realm(config); - cancellationToken?.onCancel(() { - realm.close(); - }); - - if (config is FlexibleSyncConfiguration) { - final session = realm.syncSession; - if (onProgressCallback != null) { - await session - .getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork) - .forEach((syncProgress) => onProgressCallback.call(syncProgress)) - .onError((error, stackTrace) => Realm.logger.log(Level.WARNING, error)); + + try { + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + if (onProgressCallback != null) { + await _syncProgressNotifier(session, onProgressCallback, cancellationToken); + } + await session.waitForDownload(cancellationToken: cancellationToken); } - await session.waitForDownload(cancellationToken: cancellationToken); + } catch (error) { + // Make sure that the realm is closed on error + // after all the other waiting operations were completed or canceled. + // This is at the end in order to avoid the exceptions + // for acessing handles that belong to a closed Realm. + realm.close(); + rethrow; } return realm; } + static Future _syncProgressNotifier(Session session, ProgressCallback onProgressCallback, [CancellationToken? cancellationToken]) async { + StreamSubscription? subscription; + try { + final progressCompleter = Completer().makeCancellable(cancellationToken); + final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); + subscription = progressStream.listen( + (syncProgress) => onProgressCallback.call(syncProgress), + onDone: () => progressCompleter.complete(), + onError: (Object error) => progressCompleter.completeError(error), + cancelOnError: true, + ); + await progressCompleter.future; + } catch (error) { + // Make sure that StreamSubscription is cancelled on error before to continue. + // This will prevent recieving exceptions for acessing handles that belong to a closed Realm + // in case the Realm is closed before this `subsription.cancel` to complete. + await subscription?.cancel(); + } + } + static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { diff --git a/test/realm_test.dart b/test/realm_test.dart index d9f53d300..bbc987bac 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -935,7 +935,7 @@ Future main([List? args]) async { expect(realm.isClosed, false); cancellationToken.cancel(); - expect(realm.isClosed, true); + expect(realm.isClosed, false); }); baasTest('Realm open async with added data', (appConfiguration) async { From 918e7f39b5a41901ccb924d445190493fe3fccb7 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 11:11:09 +0300 Subject: [PATCH 075/113] Close realm in cancel after all others are canceled --- lib/src/realm_class.dart | 11 +++++++++-- test/realm_test.dart | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 2aa5bdce7..5d9a9ce40 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -133,8 +133,13 @@ class Realm implements Finalizable { } static Future _open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + final completer = Completer(); Realm realm = Realm(config); - + cancellationToken?.onCancel(() { + // Make sure that the realm is closed on cancel + // after all the other waiting operations were completed or canceled. + completer.isCompleted ? realm.close() : completer.future.whenComplete(() => realm.close()); + }); try { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; @@ -144,12 +149,14 @@ class Realm implements Finalizable { await session.waitForDownload(cancellationToken: cancellationToken); } } catch (error) { - // Make sure that the realm is closed on error + // Make sure that the realm is closed on any error // after all the other waiting operations were completed or canceled. // This is at the end in order to avoid the exceptions // for acessing handles that belong to a closed Realm. realm.close(); rethrow; + } finally { + completer.complete(); } return realm; } diff --git a/test/realm_test.dart b/test/realm_test.dart index bbc987bac..d9f53d300 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -935,7 +935,7 @@ Future main([List? args]) async { expect(realm.isClosed, false); cancellationToken.cancel(); - expect(realm.isClosed, false); + expect(realm.isClosed, true); }); baasTest('Realm open async with added data', (appConfiguration) async { From a68c6dc32f2ec0d7a557112903ab1b058846e608 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 11:46:14 +0300 Subject: [PATCH 076/113] skip execution in completer is completed --- lib/src/native/realm_core.dart | 10 ++++++---- lib/src/realm_class.dart | 16 +++++++++------- test/realm_test.dart | 2 +- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 5e4dc5c6d..f834dc0b4 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1779,10 +1779,12 @@ class _RealmCore { Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { final completer = Completer().makeCancellable(cancellationToken); - final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); - final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); - _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, - userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); + if (!completer.isCompleted) { + final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); + final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); + _realmLib.realm_sync_session_wait_for_download_completion(session.handle._pointer, _realmLib.addresses.realm_dart_sync_wait_for_completion_callback, + userdata.cast(), _realmLib.addresses.realm_dart_userdata_async_free); + } return completer.future; } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 5d9a9ce40..133a6a496 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -165,13 +165,15 @@ class Realm implements Finalizable { StreamSubscription? subscription; try { final progressCompleter = Completer().makeCancellable(cancellationToken); - final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); - subscription = progressStream.listen( - (syncProgress) => onProgressCallback.call(syncProgress), - onDone: () => progressCompleter.complete(), - onError: (Object error) => progressCompleter.completeError(error), - cancelOnError: true, - ); + if (!progressCompleter.isCompleted) { + final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); + subscription = progressStream.listen( + (syncProgress) => onProgressCallback.call(syncProgress), + onDone: () => progressCompleter.complete(), + onError: (Object error) => progressCompleter.completeError(error), + cancelOnError: true, + ); + } await progressCompleter.future; } catch (error) { // Make sure that StreamSubscription is cancelled on error before to continue. diff --git a/test/realm_test.dart b/test/realm_test.dart index d9f53d300..0727c045b 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -963,7 +963,7 @@ Future main([List? args]) async { expect(transferredBytes > 2000, isTrue); }); - baasTest('Realm open async with added data, get progres and swith cancel', (appConfiguration) async { + baasTest('Realm open async with added data, get progres and cancel', (appConfiguration) async { final app = App(appConfiguration); final config = await addDataToAtlas(app); From 9d44a4e1ff6e256b85d59c5376b037c1ee6f47a1 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 16:30:51 +0300 Subject: [PATCH 077/113] Fix tests --- lib/src/realm_class.dart | 56 ++++++------ test/realm_test.dart | 178 +++++++++++++++++++++------------------ 2 files changed, 122 insertions(+), 112 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 133a6a496..b3753401c 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -127,20 +127,20 @@ class Realm implements Finalizable { /// /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. + // static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + // return await CancellableFuture.fromFutureFunction( + // () => _open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback), cancellationToken); + // } + static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - return await CancellableFuture.fromFutureFunction( - () => _open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback), cancellationToken); - } - - static Future _open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - final completer = Completer(); - Realm realm = Realm(config); - cancellationToken?.onCancel(() { - // Make sure that the realm is closed on cancel - // after all the other waiting operations were completed or canceled. - completer.isCompleted ? realm.close() : completer.future.whenComplete(() => realm.close()); - }); + final cancellableCompleter = Completer().makeCancellable(cancellationToken); try { + Realm realm = Realm(config); + cancellableCompleter.future.catchError((Object error) { + realm.close(); + return realm; + }); + if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { @@ -148,17 +148,11 @@ class Realm implements Finalizable { } await session.waitForDownload(cancellationToken: cancellationToken); } + if (!cancellableCompleter.isCompleted) cancellableCompleter.complete(realm); } catch (error) { - // Make sure that the realm is closed on any error - // after all the other waiting operations were completed or canceled. - // This is at the end in order to avoid the exceptions - // for acessing handles that belong to a closed Realm. - realm.close(); - rethrow; - } finally { - completer.complete(); + if (!cancellableCompleter.isCompleted) cancellableCompleter.completeError(error); } - return realm; + return cancellableCompleter.future; } static Future _syncProgressNotifier(Session session, ProgressCallback onProgressCallback, [CancellationToken? cancellationToken]) async { @@ -792,13 +786,13 @@ extension $CancelableCompleter on Completer { /// * `futureFunction`- a function executung a Future that has to be canceled. /// * `cancellationToken` - [CancellationToken] that is used to cancel the Future. /// {@category Realm} -class CancellableFuture { - static Future fromFutureFunction(Future Function() futureFunction, [CancellationToken? cancellationToken]) async { - if (cancellationToken != null) { - final completer = Completer().makeCancellable(cancellationToken); - return await Future.any([completer.future, futureFunction()]); - } else { - return await futureFunction(); - } - } -} +// class CancellableFuture { +// static Future fromFutureFunction(Future Function() futureFunction, [CancellationToken? cancellationToken]) async { +// if (cancellationToken != null) { +// final completer = Completer().makeCancellable(cancellationToken); +// return await Future.any([completer.future, futureFunction()]); +// } else { +// return await futureFunction(); +// } +// } +// } diff --git a/test/realm_test.dart b/test/realm_test.dart index 0727c045b..6ea737c7e 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -789,7 +789,7 @@ Future main([List? args]) async { expect(stored.location.name, 'Europe/Copenhagen'); }); - baasTest('Realm open async for flexibleSync configuration', (appConfiguration) async { + baasTest('Realm.open (flexibleSync)', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -799,30 +799,31 @@ Future main([List? args]) async { expect(realm.isClosed, false); }); - test('Realm open async for local configuration', () async { + test('Realm.open (local)', () async { final configuration = Configuration.local([Car.schema]); final realm = await RealmA.open(configuration); expect(realm.isClosed, false); }); - baasTest('Realm open async and get progress', (appConfiguration) async { + test('Realm.open (local) - cancel before open', () async { + final configuration = Configuration.local([Car.schema]); + var cancellationToken = CancellationToken(); + cancellationToken.cancel(); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + }); + + baasTest('Realm.open (flexibleSync) - cancel before open', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - int printCount = 0; - - var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - printCount++; - }); - - expect(syncedRealm.isClosed, false); - expect(printCount, isNot(0)); + var cancellationToken = CancellationToken(); + cancellationToken.cancel(); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); - baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - cancel 0 miliseconds after open', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -830,18 +831,11 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = RealmA.open(configuration, cancellationToken: cancellationToken); - cancellationToken.cancel(); - await expectLater(realm, throwsA(isA())); + final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); + await expectLater(cancelOrRealm, true); }); - test('Realm open async and cancel before Realm.open for local configuration', () async { - final configuration = Configuration.local([Car.schema]); - var cancellationToken = CancellationToken(); - cancellationToken.cancel(); - await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); - }); - - baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open twice the same realm with the same CancelationToken cancels all', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -849,13 +843,16 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); + final cancelOrRealm1 = await _cancelOrRealm(realm1, cancellationToken, 0); + final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); - cancellationToken.cancel(); - await expectLater(realm1, throwsA(isA())); - await expectLater(realm2, throwsA(isA())); + final cancelOrRealm2 = await _cancelOrRealm(realm2, cancellationToken, 0); + + await expectLater(cancelOrRealm1, true); + await expectLater(cancelOrRealm2, true); }); - baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open twice the same realm and cancel the first only', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -867,22 +864,15 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken2); - final validFuture = Future.delayed(Duration(milliseconds: 200), () async { - final openedRealm = await realm2; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); - }); - - final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { - cancellationToken1.cancel(); - await expectLater(realm1, throwsA(isA())); - }); + final cancelOrRealm = await _cancelOrRealm(realm1, cancellationToken1, 0); + expect(cancelOrRealm, true); - await validFuture; - await canceledFuture; + final openedRealm = await realm2; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); }); - baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous()); @@ -893,91 +883,116 @@ Future main([List? args]) async { final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); - final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); - final validFuture = Future.delayed(Duration(milliseconds: 200), () async { - final openedRealm = await realm1; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); - }); - - final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { - cancellationToken2.cancel(); - await expectLater(realm2, throwsA(isA())); - }); + final cancelOrRealm = await _cancelOrRealm(realm2, cancellationToken2, 0); + expect(cancelOrRealm, true); - await validFuture; - await canceledFuture; + final openedRealm = await realm1; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); }); - baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - cancel after realm is returned is no-op', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); + final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); + + expect(realm, isNotNull); + expect(realm.isClosed, false); + cancellationToken.cancel(); - await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + expect(realm.isClosed, false); }); - baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen for download progress of an empty realm', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); - final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); + int printCount = 0; - expect(realm, isNotNull); - expect(realm.isClosed, false); + var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { + print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + printCount++; + }); - cancellationToken.cancel(); - expect(realm.isClosed, true); + expect(syncedRealm.isClosed, false); + expect(printCount, isNot(0)); }); - baasTest('Realm open async with added data', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await addDataToAtlas(app); + final config = await _addDataToAtlas(app); var syncedRealm = await RealmA.open(config); expect(syncedRealm.isClosed, false); }); - baasTest('Realm open async with added data and get progress', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen for download progress of a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await addDataToAtlas(app); + final config = await _addDataToAtlas(app); int printCount = 0; int transferredBytes = 0; var syncedRealm = await RealmA.open(config, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); printCount++; transferredBytes = syncProgress.transferredBytes; }); expect(syncedRealm.isClosed, false); expect(printCount, isNot(0)); - expect(transferredBytes > 2000, isTrue); + expect(transferredBytes > 20, isTrue); //19 bytes is the empty realm }); - baasTest('Realm open async with added data, get progres and cancel', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen and cancel download progress of a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await addDataToAtlas(app); + final config = await _addDataToAtlas(app); var cancellationToken = CancellationToken(); final realm = RealmA.open(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); - cancellationToken.cancel(); - await expectLater(realm, throwsA(isA())); + final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); + expect(cancelOrRealm, true); }); } -Future addDataToAtlas(App app) async { +Future _cancelOrRealm(Future realm, CancellationToken cancellationToken, int cancelAfterMilliseconds) async { + final cancellationFuture = Future.delayed( + Duration(milliseconds: cancelAfterMilliseconds), + () => cancellationToken.cancel(), + ); + + final realmCheck = realm.then( + (value) { + expect(value.isClosed, false); + print("REALM CHECK: Realm returned before cancellation."); + return true; + }, + ).onError( + (error, stackTrace) { + print("REALM CHECK: Cancelled before to return the Realm."); + return true; + }, + test: (error) => error is CancelledException, + ).catchError((Object error) { + print("REALM CHECK: Unhandled exception."); + throw error; + }, test: (error) => error is! CancelledException); + + await cancellationFuture; + return realmCheck; +} + +Future _addDataToAtlas(App app) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config1 = Configuration.flexibleSync(user1, [Task.schema]); final realm1 = getRealm(config1); @@ -993,13 +1008,14 @@ Future addDataToAtlas(App app) async { realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); await realm2.subscriptions.waitForSynchronization(); } - realm2.write(() { - for (var i = 0; i < 100; i++) { - realm2.add(Task(ObjectId())); - } - }); - await realm2.subscriptions.waitForSynchronization(); - await realm2.syncSession.waitForUpload(); + if (realm2.all().isEmpty) { + realm2.write(() { + for (var i = 0; i < 100; i++) { + realm2.add(Task(ObjectId())); + } + }); + await realm2.syncSession.waitForUpload(); + } realm2.close(); return config1; } From e281a40a94bea30807cce9fe3dcd44fdc7755945 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 17:00:02 +0300 Subject: [PATCH 078/113] Implement CancellableCompleter --- lib/src/native/realm_core.dart | 2 +- lib/src/realm_class.dart | 69 +++++++++++++++++----------------- 2 files changed, 36 insertions(+), 35 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index f834dc0b4..c78eaf402 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1778,7 +1778,7 @@ class _RealmCore { } Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { - final completer = Completer().makeCancellable(cancellationToken); + final completer = Completer().asCancellable(cancellationToken); if (!completer.isCompleted) { final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); final userdata = _realmLib.realm_dart_userdata_async_new(completer, callback.cast(), scheduler.handle._pointer); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index b3753401c..5512ba703 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -127,13 +127,8 @@ class Realm implements Finalizable { /// /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with an `error` if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. - // static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - // return await CancellableFuture.fromFutureFunction( - // () => _open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback), cancellationToken); - // } - static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - final cancellableCompleter = Completer().makeCancellable(cancellationToken); + final cancellableCompleter = CancellableCompleter(cancellationToken); try { Realm realm = Realm(config); cancellableCompleter.future.catchError((Object error) { @@ -148,9 +143,9 @@ class Realm implements Finalizable { } await session.waitForDownload(cancellationToken: cancellationToken); } - if (!cancellableCompleter.isCompleted) cancellableCompleter.complete(realm); + cancellableCompleter.complete(realm); } catch (error) { - if (!cancellableCompleter.isCompleted) cancellableCompleter.completeError(error); + cancellableCompleter.completeError(error); } return cancellableCompleter.future; } @@ -158,7 +153,7 @@ class Realm implements Finalizable { static Future _syncProgressNotifier(Session session, ProgressCallback onProgressCallback, [CancellationToken? cancellationToken]) async { StreamSubscription? subscription; try { - final progressCompleter = Completer().makeCancellable(cancellationToken); + final progressCompleter = Completer().asCancellable(cancellationToken); if (!progressCompleter.isCompleted) { final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); subscription = progressStream.listen( @@ -757,42 +752,48 @@ class CancellationToken { } } -/// @nodoc -extension $CancelableCompleter on Completer { - Completer makeCancellable(CancellationToken? cancellationToken) { +class CancellableCompleter implements Completer { + final Completer _completer; + final CancellationToken? cancellationToken; + + CancellableCompleter(this.cancellationToken, [Completer? completer]) : _completer = completer ?? Completer() { if (cancellationToken != null) { - if (cancellationToken.isCanceled == true) { + if (cancellationToken!.isCanceled == true) { cancel(cancellationToken); } else { - cancellationToken.onCancel(() { + cancellationToken!.onCancel(() { cancel(cancellationToken); }); } } - return this; } void cancel(CancellationToken? cancellationToken) { - if (cancellationToken != null && !isCompleted) { - completeError(cancellationToken.cancelException); + if (cancellationToken != null && !_completer.isCompleted) { + _completer.completeError(cancellationToken.cancelException); } } + + @override + void complete([FutureOr? value]) { + if (!_completer.isCompleted) _completer.complete(value); + } + + @override + void completeError(Object error, [StackTrace? stackTrace]) { + if (!_completer.isCompleted) _completer.completeError(error, stackTrace); + } + + @override + Future get future => _completer.future; + + @override + bool get isCompleted => _completer.isCompleted; } -/// [CancellableFuture] provides a static method [fromFutureFunction] that builds cancellable Future -/// from Future function. -/// -/// fromFutureFunction arguments are: -/// * `futureFunction`- a function executung a Future that has to be canceled. -/// * `cancellationToken` - [CancellationToken] that is used to cancel the Future. -/// {@category Realm} -// class CancellableFuture { -// static Future fromFutureFunction(Future Function() futureFunction, [CancellationToken? cancellationToken]) async { -// if (cancellationToken != null) { -// final completer = Completer().makeCancellable(cancellationToken); -// return await Future.any([completer.future, futureFunction()]); -// } else { -// return await futureFunction(); -// } -// } -// } +/// @nodoc +extension $CancellableCompleterExtension on Completer { + CancellableCompleter asCancellable(CancellationToken? cancellationToken) { + return CancellableCompleter(cancellationToken, this); + } +} From aee17c33fb22a651d4d72f2e1a4ee41e92ff21f5 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 17:15:16 +0300 Subject: [PATCH 079/113] Fix tests and add extension asCancellable --- lib/src/realm_class.dart | 62 ++++---------- test/realm_test.dart | 178 +++++++++++++++++++++------------------ 2 files changed, 114 insertions(+), 126 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d420afbb4..30652fa88 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -130,8 +130,14 @@ class Realm implements Finalizable { /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with a [CancelledException] if operation is canceled. /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - Realm realm = (() => Realm(config)).asCancellable(cancellationToken); + final cancellableCompleter = CancellableCompleter(cancellationToken); try { + Realm realm = Realm(config); + cancellableCompleter.future.catchError((Object error) { + realm.close(); + return realm; + }); + if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { @@ -139,22 +145,18 @@ class Realm implements Finalizable { } await session.waitForDownload(cancellationToken: cancellationToken); } + cancellableCompleter.complete(realm); } catch (error) { - // Make sure that the realm is closed on error - // after all the other waiting operations were completed or canceled. - // This is at the end in order to avoid the exceptions - // for acessing handles that belong to a closed Realm. - realm.close(); - rethrow; + cancellableCompleter.completeError(error); } - return realm; + return cancellableCompleter.future; } static Future _syncProgressNotifier(Session session, ProgressCallback onProgressCallback, [CancellationToken? cancellationToken]) async { StreamSubscription? subscription; try { - final progressCompleter = CancellableCompleter(cancellationToken); - if (!progressCompleter.isCancelled) { + final progressCompleter = Completer().asCancellable(cancellationToken); + if (!progressCompleter.isCompleted) { final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); subscription = progressStream.listen( (syncProgress) => onProgressCallback.call(syncProgress), @@ -166,7 +168,7 @@ class Realm implements Finalizable { await progressCompleter.future; } catch (error) { // Make sure that StreamSubscription is cancelled on error before to continue. - // This will prevent recieving exceptions for acessing handles that belong to a closed Realm + // This will prevent receiving exceptions for acessing handles that belong to a closed Realm // in case the Realm is closed before this `subsription.cancel` to complete. await subscription?.cancel(); } @@ -718,39 +720,9 @@ class MigrationRealm extends DynamicRealm { /// {@category Realm} typedef ProgressCallback = void Function(SyncProgress syncProgress); -extension $CancellableFunc on T Function() { - T asCancellable(CancellationToken? cancellationToken, {Function(T? result)? onCancel}) { - final cancellableFunction = _CancellableFunction( - cancellationToken, - function: () => this(), - whenCancel: (result, ex, [stackTrace]) => onCancel != null ? onCancel(result) : null, - ); - return cancellableFunction.result; - } -} - -class _CancellableFunction with Cancellable { - T result; - _CancellableFunction(CancellationToken? cancellationToken, {required this.function, this.whenCancel}) - : _cancellationToken = cancellationToken, - result = function() { - if (!maybeAttach(_cancellationToken)) { - throw _cancellationToken!.exception; - } - } - - final CancellationToken? _cancellationToken; - final Function(T? result, Exception cancelException, [StackTrace? stackTrace])? whenCancel; - final T Function() function; - - void complete() { - _cancellationToken?.detach(this); - } - - @override - onCancel(Exception cancelException, [StackTrace? stackTrace]) { - if (whenCancel != null) { - whenCancel!(result, cancelException, stackTrace); - } +/// @nodoc +extension $CancellableCompleterExtension on Completer { + CancellableCompleter asCancellable(CancellationToken? cancellationToken, {OnCancelCallback? onCancel}) { + return CancellableCompleter(cancellationToken, onCancel: onCancel); } } diff --git a/test/realm_test.dart b/test/realm_test.dart index bbc987bac..6ea737c7e 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -789,7 +789,7 @@ Future main([List? args]) async { expect(stored.location.name, 'Europe/Copenhagen'); }); - baasTest('Realm open async for flexibleSync configuration', (appConfiguration) async { + baasTest('Realm.open (flexibleSync)', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -799,30 +799,31 @@ Future main([List? args]) async { expect(realm.isClosed, false); }); - test('Realm open async for local configuration', () async { + test('Realm.open (local)', () async { final configuration = Configuration.local([Car.schema]); final realm = await RealmA.open(configuration); expect(realm.isClosed, false); }); - baasTest('Realm open async and get progress', (appConfiguration) async { + test('Realm.open (local) - cancel before open', () async { + final configuration = Configuration.local([Car.schema]); + var cancellationToken = CancellationToken(); + cancellationToken.cancel(); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + }); + + baasTest('Realm.open (flexibleSync) - cancel before open', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - int printCount = 0; - - var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - printCount++; - }); - - expect(syncedRealm.isClosed, false); - expect(printCount, isNot(0)); + var cancellationToken = CancellationToken(); + cancellationToken.cancel(); + await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); - baasTest('Realm open async and cancel for flexibleSync configuration', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - cancel 0 miliseconds after open', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -830,18 +831,11 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm = RealmA.open(configuration, cancellationToken: cancellationToken); - cancellationToken.cancel(); - await expectLater(realm, throwsA(isA())); + final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); + await expectLater(cancelOrRealm, true); }); - test('Realm open async and cancel before Realm.open for local configuration', () async { - final configuration = Configuration.local([Car.schema]); - var cancellationToken = CancellationToken(); - cancellationToken.cancel(); - await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); - }); - - baasTest('Realm open async with the same CancelationToken cancels all', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open twice the same realm with the same CancelationToken cancels all', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -849,13 +843,16 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); + final cancelOrRealm1 = await _cancelOrRealm(realm1, cancellationToken, 0); + final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); - cancellationToken.cancel(); - await expectLater(realm1, throwsA(isA())); - await expectLater(realm2, throwsA(isA())); + final cancelOrRealm2 = await _cancelOrRealm(realm2, cancellationToken, 0); + + await expectLater(cancelOrRealm1, true); + await expectLater(cancelOrRealm2, true); }); - baasTest('Realm open async - open twice the same realm and cancel the first only', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open twice the same realm and cancel the first only', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -867,22 +864,15 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken2); - final validFuture = Future.delayed(Duration(milliseconds: 200), () async { - final openedRealm = await realm2; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); - }); - - final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { - cancellationToken1.cancel(); - await expectLater(realm1, throwsA(isA())); - }); + final cancelOrRealm = await _cancelOrRealm(realm1, cancellationToken1, 0); + expect(cancelOrRealm, true); - await validFuture; - await canceledFuture; + final openedRealm = await realm2; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); }); - baasTest('Realm open async - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous()); @@ -893,91 +883,116 @@ Future main([List? args]) async { final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); - final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); - final validFuture = Future.delayed(Duration(milliseconds: 200), () async { - final openedRealm = await realm1; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); - }); - - final canceledFuture = Future.delayed(Duration(milliseconds: 300), () async { - cancellationToken2.cancel(); - await expectLater(realm2, throwsA(isA())); - }); + final cancelOrRealm = await _cancelOrRealm(realm2, cancellationToken2, 0); + expect(cancelOrRealm, true); - await validFuture; - await canceledFuture; + final openedRealm = await realm1; + expect(openedRealm, isNotNull); + expect(openedRealm.isClosed, false); }); - baasTest('Realm open async - CancellationToken.cancel before Realm.open', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - cancel after realm is returned is no-op', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); + final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); + + expect(realm, isNotNull); + expect(realm.isClosed, false); + cancellationToken.cancel(); - await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + expect(realm.isClosed, false); }); - baasTest('Realm open async - CancellationToken.cancel after realm is obtained', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen for download progress of an empty realm', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); - final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); + int printCount = 0; - expect(realm, isNotNull); - expect(realm.isClosed, false); + var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { + print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + printCount++; + }); - cancellationToken.cancel(); - expect(realm.isClosed, false); + expect(syncedRealm.isClosed, false); + expect(printCount, isNot(0)); }); - baasTest('Realm open async with added data', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await addDataToAtlas(app); + final config = await _addDataToAtlas(app); var syncedRealm = await RealmA.open(config); expect(syncedRealm.isClosed, false); }); - baasTest('Realm open async with added data and get progress', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen for download progress of a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await addDataToAtlas(app); + final config = await _addDataToAtlas(app); int printCount = 0; int transferredBytes = 0; var syncedRealm = await RealmA.open(config, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); printCount++; transferredBytes = syncProgress.transferredBytes; }); expect(syncedRealm.isClosed, false); expect(printCount, isNot(0)); - expect(transferredBytes > 2000, isTrue); + expect(transferredBytes > 20, isTrue); //19 bytes is the empty realm }); - baasTest('Realm open async with added data, get progres and swith cancel', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen and cancel download progress of a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await addDataToAtlas(app); + final config = await _addDataToAtlas(app); var cancellationToken = CancellationToken(); final realm = RealmA.open(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); - cancellationToken.cancel(); - await expectLater(realm, throwsA(isA())); + final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); + expect(cancelOrRealm, true); }); } -Future addDataToAtlas(App app) async { +Future _cancelOrRealm(Future realm, CancellationToken cancellationToken, int cancelAfterMilliseconds) async { + final cancellationFuture = Future.delayed( + Duration(milliseconds: cancelAfterMilliseconds), + () => cancellationToken.cancel(), + ); + + final realmCheck = realm.then( + (value) { + expect(value.isClosed, false); + print("REALM CHECK: Realm returned before cancellation."); + return true; + }, + ).onError( + (error, stackTrace) { + print("REALM CHECK: Cancelled before to return the Realm."); + return true; + }, + test: (error) => error is CancelledException, + ).catchError((Object error) { + print("REALM CHECK: Unhandled exception."); + throw error; + }, test: (error) => error is! CancelledException); + + await cancellationFuture; + return realmCheck; +} + +Future _addDataToAtlas(App app) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config1 = Configuration.flexibleSync(user1, [Task.schema]); final realm1 = getRealm(config1); @@ -993,13 +1008,14 @@ Future addDataToAtlas(App app) async { realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); await realm2.subscriptions.waitForSynchronization(); } - realm2.write(() { - for (var i = 0; i < 100; i++) { - realm2.add(Task(ObjectId())); - } - }); - await realm2.subscriptions.waitForSynchronization(); - await realm2.syncSession.waitForUpload(); + if (realm2.all().isEmpty) { + realm2.write(() { + for (var i = 0; i < 100; i++) { + realm2.add(Task(ObjectId())); + } + }); + await realm2.syncSession.waitForUpload(); + } realm2.close(); return config1; } From 88456eda85cb9c1a547040461127b670fe1a561f Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 19:19:22 +0300 Subject: [PATCH 080/113] Self code review changes --- lib/src/native/realm_core.dart | 2 +- lib/src/realm_class.dart | 10 +++++----- lib/src/session.dart | 2 +- src/realm_dart_sync.cpp | 2 +- src/realm_dart_sync.h | 2 +- test/realm_test.dart | 23 ++++++++++++----------- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 388e0a7c9..c0bfedd4a 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1777,7 +1777,7 @@ class _RealmCore { return completer.future; } - Future sessionWaitForDownload(Session session, {CancellationToken? cancellationToken}) { + Future sessionWaitForDownload(Session session, [CancellationToken? cancellationToken]) { final completer = CancellableCompleter(cancellationToken); if (!completer.isCancelled) { final callback = Pointer.fromFunction)>(_sessionWaitCompletionCallback); diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 30652fa88..df99e0111 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -119,16 +119,16 @@ class Realm implements Finalizable { /// A method for asynchronously opening a [Realm]. /// - /// If the configuration is [FlexibleSyncConfiguration], the realm will be downloaded and fully + /// When the configuration is [FlexibleSyncConfiguration], the realm will be downloaded and fully /// synchronized with the server prior to the completion of the returned [Future]. - /// This method could be called also for opening a local [Realm]. + /// This method could be called also for opening a local [Realm] with [LocalConfiguration]. /// /// * `config`- a configuration object that describes the realm. /// * `cancellationToken` - an optional [CancellationToken] used to cancel the operation. /// * `onProgressCallback` - a callback for receiving download progress notifications for synced [Realm]s. /// - /// Returns [Future] that completes with the `realm` once the remote realm is fully synchronized or with a [CancelledException] if operation is canceled. - /// When the configuration is [LocalConfiguration] this completes right after the local realm is opened or operation is canceled. + /// Returns `Future` that completes with the [Realm] once the remote [Realm] is fully synchronized or with a [CancelledException] if operation is canceled. + /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or operation is canceled. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { final cancellableCompleter = CancellableCompleter(cancellationToken); try { @@ -143,7 +143,7 @@ class Realm implements Finalizable { if (onProgressCallback != null) { await _syncProgressNotifier(session, onProgressCallback, cancellationToken); } - await session.waitForDownload(cancellationToken: cancellationToken); + await session.waitForDownload(cancellationToken); } cancellableCompleter.complete(realm); } catch (error) { diff --git a/lib/src/session.dart b/lib/src/session.dart index 7bed6874e..b2f1201dc 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -61,7 +61,7 @@ class Session implements Finalizable { Future waitForUpload() => realmCore.sessionWaitForUpload(this); /// Waits for the [Session] to finish all pending downloads. - Future waitForDownload({CancellationToken? cancellationToken}) => realmCore.sessionWaitForDownload(this, cancellationToken: cancellationToken); + Future waitForDownload([CancellationToken? cancellationToken]) => realmCore.sessionWaitForDownload(this, cancellationToken); /// Gets a [Stream] of [SyncProgress] that can be used to track upload or download progress. Stream getProgressStream(ProgressDirection direction, ProgressMode mode) { diff --git a/src/realm_dart_sync.cpp b/src/realm_dart_sync.cpp index c6bd8b1c0..45eea0548 100644 --- a/src/realm_dart_sync.cpp +++ b/src/realm_dart_sync.cpp @@ -137,4 +137,4 @@ RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userda ud->scheduler->invoke([ud, state]() { (reinterpret_cast(ud->dart_callback))(ud->handle, state); }); -} \ No newline at end of file +} diff --git a/src/realm_dart_sync.h b/src/realm_dart_sync.h index 70529cffc..90c50e2aa 100644 --- a/src/realm_dart_sync.h +++ b/src/realm_dart_sync.h @@ -34,4 +34,4 @@ RLM_API void realm_dart_sync_connection_state_changed_callback(realm_userdata_t realm_sync_connection_state_e old_state, realm_sync_connection_state_e new_state); -RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userdata_t userdata, realm_flx_sync_subscription_set_state_e state); \ No newline at end of file +RLM_API void realm_dart_sync_on_subscription_state_changed_callback(realm_userdata_t userdata, realm_flx_sync_subscription_set_state_e state); diff --git a/test/realm_test.dart b/test/realm_test.dart index 6ea737c7e..03d788ac2 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -18,12 +18,12 @@ // ignore_for_file: unused_local_variable, avoid_relative_lib_imports -import 'dart:async'; import 'dart:io'; import 'package:test/test.dart' hide test, throws; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/data/latest.dart' as tz; import '../lib/realm.dart'; + import 'test.dart'; Future main([List? args]) async { @@ -842,11 +842,14 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); - final cancelOrRealm1 = await _cancelOrRealm(realm1, cancellationToken, 0); + Future.delayed(Duration(milliseconds: 1), () => cancellationToken.cancel()); + + final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); - final cancelOrRealm2 = await _cancelOrRealm(realm2, cancellationToken, 0); + + final cancelOrRealm1 = await _cancelOrRealm(realm1, cancellationToken); + final cancelOrRealm2 = await _cancelOrRealm(realm2, cancellationToken); await expectLater(cancelOrRealm1, true); await expectLater(cancelOrRealm2, true); @@ -935,11 +938,11 @@ Future main([List? args]) async { baasTest('Realm.open (flexibleSync) - listen for download progress of a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await _addDataToAtlas(app); int printCount = 0; int transferredBytes = 0; + var syncedRealm = await RealmA.open(config, onProgressCallback: (syncProgress) { print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); printCount++; @@ -953,7 +956,6 @@ Future main([List? args]) async { baasTest('Realm.open (flexibleSync) - listen and cancel download progress of a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await _addDataToAtlas(app); var cancellationToken = CancellationToken(); @@ -965,11 +967,10 @@ Future main([List? args]) async { }); } -Future _cancelOrRealm(Future realm, CancellationToken cancellationToken, int cancelAfterMilliseconds) async { - final cancellationFuture = Future.delayed( - Duration(milliseconds: cancelAfterMilliseconds), - () => cancellationToken.cancel(), - ); +Future _cancelOrRealm(Future realm, CancellationToken cancellationToken, [int? cancelAfterMilliseconds]) async { + final cancellationFuture = cancelAfterMilliseconds == null + ? Future.value() + : Future.delayed(Duration(milliseconds: cancelAfterMilliseconds), () => cancellationToken.cancel()); final realmCheck = realm.then( (value) { From 834ca0144cf78995345777b7d6293786331d2e10 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 19:42:51 +0300 Subject: [PATCH 081/113] fix one test --- test/realm_test.dart | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 03d788ac2..3346431d3 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -951,7 +951,7 @@ Future main([List? args]) async { expect(syncedRealm.isClosed, false); expect(printCount, isNot(0)); - expect(transferredBytes > 20, isTrue); //19 bytes is the empty realm + expect(transferredBytes > 19, isTrue); //19 bytes is the empty realm }); baasTest('Realm.open (flexibleSync) - listen and cancel download progress of a populated realm', (appConfiguration) async { @@ -997,16 +997,19 @@ Future _addDataToAtlas(App app) async { final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config1 = Configuration.flexibleSync(user1, [Task.schema]); final realm1 = getRealm(config1); - if (realm1.subscriptions.find(realm1.all()) == null) { - realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm1.all())); + final query1 = realm1.all(); + if (realm1.subscriptions.find(query1) == null) { + realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query1)); await realm1.subscriptions.waitForSynchronization(); } final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config2 = Configuration.flexibleSync(user2, [Task.schema]); final realm2 = getRealm(config2); - if (realm2.subscriptions.find(realm2.all()) == null) { - realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(realm2.all())); + final query2 = realm2.all(); + + if (realm2.subscriptions.find(query2) == null) { + realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query2)); await realm2.subscriptions.waitForSynchronization(); } if (realm2.all().isEmpty) { @@ -1015,8 +1018,8 @@ Future _addDataToAtlas(App app) async { realm2.add(Task(ObjectId())); } }); - await realm2.syncSession.waitForUpload(); } + await realm2.syncSession.waitForUpload(); realm2.close(); return config1; } From 59b38c2cac7c53908a7077037faafe775291aa3e Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 6 Oct 2022 21:23:41 +0300 Subject: [PATCH 082/113] Update lib/src/realm_class.dart Co-authored-by: Nikola Irinchev --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index df99e0111..d4eb01d1e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -132,7 +132,7 @@ class Realm implements Finalizable { static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { final cancellableCompleter = CancellableCompleter(cancellationToken); try { - Realm realm = Realm(config); + final realm = Realm(config); cancellableCompleter.future.catchError((Object error) { realm.close(); return realm; From 69dac4dc8ea04d0e11a88eb9a460d0ddfa43bc2d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 6 Oct 2022 21:40:15 +0300 Subject: [PATCH 083/113] Code review change --- lib/src/realm_class.dart | 19 ++++++++++++++++--- test/realm_test.dart | 38 +++++++++++++++++++------------------- test/test.dart | 24 +++++++++++------------- 3 files changed, 46 insertions(+), 35 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index df99e0111..f2f57deba 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -140,10 +140,23 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; - if (onProgressCallback != null) { - await _syncProgressNotifier(session, onProgressCallback, cancellationToken); + StreamSubscription? subscription; + try { + if (onProgressCallback != null) { + subscription = session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .listen( + (syncProgress) => onProgressCallback.call(syncProgress), + cancelOnError: true, + ); + } + await session.waitForDownload(cancellationToken); + } finally { + await subscription?.cancel(); } - await session.waitForDownload(cancellationToken); } cancellableCompleter.complete(realm); } catch (error) { diff --git a/test/realm_test.dart b/test/realm_test.dart index 3346431d3..9a112662c 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -795,13 +795,13 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - final realm = await RealmA.open(configuration); + final realm = await getRealmAsync(configuration); expect(realm.isClosed, false); }); test('Realm.open (local)', () async { final configuration = Configuration.local([Car.schema]); - final realm = await RealmA.open(configuration); + final realm = await getRealmAsync(configuration); expect(realm.isClosed, false); }); @@ -809,7 +809,7 @@ Future main([List? args]) async { final configuration = Configuration.local([Car.schema]); var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(getRealmAsync(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm.open (flexibleSync) - cancel before open', (appConfiguration) async { @@ -820,7 +820,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); cancellationToken.cancel(); - await expectLater(RealmA.open(configuration, cancellationToken: cancellationToken), throwsA(isA())); + await expectLater(getRealmAsync(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); baasTest('Realm.open (flexibleSync) - cancel 0 miliseconds after open', (appConfiguration) async { @@ -830,7 +830,7 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm = RealmA.open(configuration, cancellationToken: cancellationToken); + final realm = getRealmAsync(configuration, cancellationToken: cancellationToken); final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); await expectLater(cancelOrRealm, true); }); @@ -845,8 +845,8 @@ Future main([List? args]) async { Future.delayed(Duration(milliseconds: 1), () => cancellationToken.cancel()); - final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken); - final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken); + final realm1 = getRealmAsync(configuration, cancellationToken: cancellationToken); + final realm2 = getRealmAsync(configuration, cancellationToken: cancellationToken); final cancelOrRealm1 = await _cancelOrRealm(realm1, cancellationToken); final cancelOrRealm2 = await _cancelOrRealm(realm2, cancellationToken); @@ -862,10 +862,10 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken1 = CancellationToken(); - final realm1 = RealmA.open(configuration, cancellationToken: cancellationToken1); + final realm1 = getRealmAsync(configuration, cancellationToken: cancellationToken1); var cancellationToken2 = CancellationToken(); - final realm2 = RealmA.open(configuration, cancellationToken: cancellationToken2); + final realm2 = getRealmAsync(configuration, cancellationToken: cancellationToken2); final cancelOrRealm = await _cancelOrRealm(realm1, cancellationToken1, 0); expect(cancelOrRealm, true); @@ -881,12 +881,12 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.anonymous()); final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); var cancellationToken1 = CancellationToken(); - final realm1 = RealmA.open(configuration1, cancellationToken: cancellationToken1); + final realm1 = getRealmAsync(configuration1, cancellationToken: cancellationToken1); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); - final realm2 = RealmA.open(configuration2, cancellationToken: cancellationToken2); + final realm2 = getRealmAsync(configuration2, cancellationToken: cancellationToken2); final cancelOrRealm = await _cancelOrRealm(realm2, cancellationToken2, 0); expect(cancelOrRealm, true); @@ -903,7 +903,7 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm = await RealmA.open(configuration, cancellationToken: cancellationToken); + final realm = await getRealmAsync(configuration, cancellationToken: cancellationToken); expect(realm, isNotNull); expect(realm.isClosed, false); @@ -920,7 +920,7 @@ Future main([List? args]) async { int printCount = 0; - var syncedRealm = await RealmA.open(configuration, onProgressCallback: (syncProgress) { + var syncedRealm = await getRealmAsync(configuration, onProgressCallback: (syncProgress) { print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); printCount++; }); @@ -932,7 +932,7 @@ Future main([List? args]) async { baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { final app = App(appConfiguration); final config = await _addDataToAtlas(app); - var syncedRealm = await RealmA.open(config); + var syncedRealm = await getRealmAsync(config); expect(syncedRealm.isClosed, false); }); @@ -943,7 +943,7 @@ Future main([List? args]) async { int printCount = 0; int transferredBytes = 0; - var syncedRealm = await RealmA.open(config, onProgressCallback: (syncProgress) { + var syncedRealm = await getRealmAsync(config, onProgressCallback: (syncProgress) { print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); printCount++; transferredBytes = syncProgress.transferredBytes; @@ -959,7 +959,7 @@ Future main([List? args]) async { final config = await _addDataToAtlas(app); var cancellationToken = CancellationToken(); - final realm = RealmA.open(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + final realm = getRealmAsync(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }); final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); @@ -1000,9 +1000,9 @@ Future _addDataToAtlas(App app) async { final query1 = realm1.all(); if (realm1.subscriptions.find(query1) == null) { realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query1)); - await realm1.subscriptions.waitForSynchronization(); } - + await realm1.subscriptions.waitForSynchronization(); + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config2 = Configuration.flexibleSync(user2, [Task.schema]); final realm2 = getRealm(config2); @@ -1010,8 +1010,8 @@ Future _addDataToAtlas(App app) async { if (realm2.subscriptions.find(query2) == null) { realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query2)); - await realm2.subscriptions.waitForSynchronization(); } + await realm2.subscriptions.waitForSynchronization(); if (realm2.all().isEmpty) { realm2.write(() { for (var i = 0; i < 100; i++) { diff --git a/test/test.dart b/test/test.dart index ebc36cc4a..047da9300 100644 --- a/test/test.dart +++ b/test/test.dart @@ -331,6 +331,17 @@ Realm getRealm(Configuration config) { return realm; } +Future getRealmAsync(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + { + if (config is FlexibleSyncConfiguration) { + config.sessionStopPolicy = SessionStopPolicy.immediately; + } + final realm = await Realm.open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback); + _openRealms.add(realm); + return realm; + } +} + /// This is needed to make sure the frozen Realm gets forcefully closed by the /// time the test ends. Realm freezeRealm(Realm realm) { @@ -571,16 +582,3 @@ Future _printPlatformInfo() async { print('Current PID $pid; OS $os, $pointerSize bit, CPU ${cpu ?? 'unknown'}'); } - -class RealmA { - static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - { - if (config is FlexibleSyncConfiguration) { - config.sessionStopPolicy = SessionStopPolicy.immediately; - } - final realm = await Realm.open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback); - _openRealms.add(realm); - return realm; - } - } -} From b025e39aa4eea11587784a6a55c160b1990f9119 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 7 Oct 2022 11:24:11 +0300 Subject: [PATCH 084/113] Remove a redundant method --- lib/src/realm_class.dart | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d74e8874f..daf68ca0c 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -165,28 +165,6 @@ class Realm implements Finalizable { return cancellableCompleter.future; } - static Future _syncProgressNotifier(Session session, ProgressCallback onProgressCallback, [CancellationToken? cancellationToken]) async { - StreamSubscription? subscription; - try { - final progressCompleter = Completer().asCancellable(cancellationToken); - if (!progressCompleter.isCompleted) { - final progressStream = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork); - subscription = progressStream.listen( - (syncProgress) => onProgressCallback.call(syncProgress), - onDone: () => progressCompleter.complete(), - onError: (Object error) => progressCompleter.completeError(error), - cancelOnError: true, - ); - } - await progressCompleter.future; - } catch (error) { - // Make sure that StreamSubscription is cancelled on error before to continue. - // This will prevent receiving exceptions for acessing handles that belong to a closed Realm - // in case the Realm is closed before this `subsription.cancel` to complete. - await subscription?.cancel(); - } - } - static RealmHandle _openRealmSync(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { From e601f58f14d3b6f2e251a27916200dcfc14f289a Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 12 Oct 2022 13:39:38 +0300 Subject: [PATCH 085/113] Code review changes --- lib/src/realm_class.dart | 59 +++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 28 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index daf68ca0c..05a460ec8 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -128,39 +128,42 @@ class Realm implements Finalizable { /// * `onProgressCallback` - a callback for receiving download progress notifications for synced [Realm]s. /// /// Returns `Future` that completes with the [Realm] once the remote [Realm] is fully synchronized or with a [CancelledException] if operation is canceled. - /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or operation is canceled. + /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advanced. + /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open asynchronouse. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { final cancellableCompleter = CancellableCompleter(cancellationToken); - try { - final realm = Realm(config); - cancellableCompleter.future.catchError((Object error) { - realm.close(); - return realm; - }); - - if (config is FlexibleSyncConfiguration) { - final session = realm.syncSession; - StreamSubscription? subscription; - try { - if (onProgressCallback != null) { - subscription = session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) - .listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); + if (!cancellableCompleter.isCancelled) { + try { + final realm = Realm(config); + cancellableCompleter.future.catchError((Object error) { + realm.close(); + return realm; + }); + + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + StreamSubscription? subscription; + try { + if (onProgressCallback != null) { + subscription = session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .listen( + (syncProgress) => onProgressCallback.call(syncProgress), + cancelOnError: true, + ); + } + await session.waitForDownload(cancellationToken); + } finally { + await subscription?.cancel(); } - await session.waitForDownload(cancellationToken); - } finally { - await subscription?.cancel(); } + cancellableCompleter.complete(realm); + } catch (error) { + cancellableCompleter.completeError(error); } - cancellableCompleter.complete(realm); - } catch (error) { - cancellableCompleter.completeError(error); } return cancellableCompleter.future; } From 45be02b4cc7b8ea58cb524892ac63a9b4559e7b0 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 12 Oct 2022 17:01:45 +0300 Subject: [PATCH 086/113] Code review changes --- lib/src/realm_class.dart | 61 +++++++++++-------------- lib/src/session.dart | 2 +- test/realm_test.dart | 98 ++++++++++++++++------------------------ 3 files changed, 68 insertions(+), 93 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 05a460ec8..8f88eb8c5 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -128,44 +128,37 @@ class Realm implements Finalizable { /// * `onProgressCallback` - a callback for receiving download progress notifications for synced [Realm]s. /// /// Returns `Future` that completes with the [Realm] once the remote [Realm] is fully synchronized or with a [CancelledException] if operation is canceled. - /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advanced. - /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open asynchronouse. + /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advance. + /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open over the constructor. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - final cancellableCompleter = CancellableCompleter(cancellationToken); - if (!cancellableCompleter.isCancelled) { - try { - final realm = Realm(config); - cancellableCompleter.future.catchError((Object error) { - realm.close(); - return realm; - }); - - if (config is FlexibleSyncConfiguration) { - final session = realm.syncSession; - StreamSubscription? subscription; - try { - if (onProgressCallback != null) { - subscription = session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) - .listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); - } - await session.waitForDownload(cancellationToken); - } finally { - await subscription?.cancel(); - } + if (cancellationToken?.isCancelled ?? false) { + return Future.error(cancellationToken!.exception); + } + final realm = Realm(config); + StreamSubscription? subscription; + try { + if (config is FlexibleSyncConfiguration) { + final session = realm.syncSession; + if (onProgressCallback != null) { + subscription = session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .listen( + (syncProgress) => onProgressCallback.call(syncProgress), + cancelOnError: true, + ); } - cancellableCompleter.complete(realm); - } catch (error) { - cancellableCompleter.completeError(error); + await session.waitForDownload(cancellationToken); } + } catch (error) { + realm.close(); + rethrow; + } finally { + await subscription?.cancel(); } - return cancellableCompleter.future; + return realm; } static RealmHandle _openRealmSync(Configuration config) { diff --git a/lib/src/session.dart b/lib/src/session.dart index b2f1201dc..bc4170bc2 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -204,7 +204,7 @@ class SessionProgressNotificationsController { } void _stop() { - if (_token == null) { + if (_token == null || _session._handle.released) { return; } diff --git a/test/realm_test.dart b/test/realm_test.dart index 9a112662c..14ab63f90 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -823,16 +823,16 @@ Future main([List? args]) async { await expectLater(getRealmAsync(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); - baasTest('Realm.open (flexibleSync) - cancel 0 miliseconds after open', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - cancel right after open', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken = CancellationToken(); - final realm = getRealmAsync(configuration, cancellationToken: cancellationToken); - final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); - await expectLater(cancelOrRealm, true); + final isRealmCancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); + cancellationToken.cancel(); + await expectLater(await isRealmCancelled, isTrue); }); baasTest('Realm.open (flexibleSync) - open twice the same realm with the same CancelationToken cancels all', (appConfiguration) async { @@ -843,16 +843,11 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); - Future.delayed(Duration(milliseconds: 1), () => cancellationToken.cancel()); - - final realm1 = getRealmAsync(configuration, cancellationToken: cancellationToken); - final realm2 = getRealmAsync(configuration, cancellationToken: cancellationToken); - - final cancelOrRealm1 = await _cancelOrRealm(realm1, cancellationToken); - final cancelOrRealm2 = await _cancelOrRealm(realm2, cancellationToken); - - await expectLater(cancelOrRealm1, true); - await expectLater(cancelOrRealm2, true); + Future.delayed(Duration(milliseconds: 10), () => cancellationToken.cancel()); + final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); + final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); + await expectLater(await isRealm1Cancelled, isTrue); + await expectLater(await isRealm2Cancelled, isTrue); }); baasTest('Realm.open (flexibleSync) - open twice the same realm and cancel the first only', (appConfiguration) async { @@ -862,17 +857,13 @@ Future main([List? args]) async { final configuration = Configuration.flexibleSync(user, [Task.schema]); var cancellationToken1 = CancellationToken(); - final realm1 = getRealmAsync(configuration, cancellationToken: cancellationToken1); + final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken1).thenIsCancelled(); var cancellationToken2 = CancellationToken(); - final realm2 = getRealmAsync(configuration, cancellationToken: cancellationToken2); - - final cancelOrRealm = await _cancelOrRealm(realm1, cancellationToken1, 0); - expect(cancelOrRealm, true); - - final openedRealm = await realm2; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); + final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken2).thenIsCancelled(); + cancellationToken1.cancel(); + await expectLater(await isRealm1Cancelled, isTrue); + await expectLater(await isRealm2Cancelled, isFalse); }); baasTest('Realm.open (flexibleSync) - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { @@ -881,19 +872,16 @@ Future main([List? args]) async { final user1 = await app.logIn(Credentials.anonymous()); final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); var cancellationToken1 = CancellationToken(); - final realm1 = getRealmAsync(configuration1, cancellationToken: cancellationToken1); + final isRealm1Cancelled = getRealmAsync(configuration1, cancellationToken: cancellationToken1).thenIsCancelled(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); var cancellationToken2 = CancellationToken(); - final realm2 = getRealmAsync(configuration2, cancellationToken: cancellationToken2); + final isRealm2Cancelled = getRealmAsync(configuration2, cancellationToken: cancellationToken2).thenIsCancelled(); - final cancelOrRealm = await _cancelOrRealm(realm2, cancellationToken2, 0); - expect(cancelOrRealm, true); - - final openedRealm = await realm1; - expect(openedRealm, isNotNull); - expect(openedRealm.isClosed, false); + cancellationToken2.cancel(); + await expectLater(await isRealm2Cancelled, isTrue); + await expectLater(await isRealm1Cancelled, isFalse); }); baasTest('Realm.open (flexibleSync) - cancel after realm is returned is no-op', (appConfiguration) async { @@ -959,38 +947,32 @@ Future main([List? args]) async { final config = await _addDataToAtlas(app); var cancellationToken = CancellationToken(); - final realm = getRealmAsync(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { + final realmIsCancelled = getRealmAsync(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - }); - final cancelOrRealm = await _cancelOrRealm(realm, cancellationToken, 0); - expect(cancelOrRealm, true); + }).thenIsCancelled(); + cancellationToken.cancel(); + await expectLater(await realmIsCancelled, isTrue); }); } -Future _cancelOrRealm(Future realm, CancellationToken cancellationToken, [int? cancelAfterMilliseconds]) async { - final cancellationFuture = cancelAfterMilliseconds == null - ? Future.value() - : Future.delayed(Duration(milliseconds: cancelAfterMilliseconds), () => cancellationToken.cancel()); - - final realmCheck = realm.then( - (value) { +extension _FutureRealm on Future { + Future thenIsCancelled() async { + return await then((value) { + expect(value, isNotNull); expect(value.isClosed, false); print("REALM CHECK: Realm returned before cancellation."); - return true; - }, - ).onError( - (error, stackTrace) { - print("REALM CHECK: Cancelled before to return the Realm."); - return true; - }, - test: (error) => error is CancelledException, - ).catchError((Object error) { - print("REALM CHECK: Unhandled exception."); - throw error; - }, test: (error) => error is! CancelledException); - - await cancellationFuture; - return realmCheck; + return false; + }).onError( + (error, stackTrace) { + print("REALM CHECK: Cancelled before to return the Realm."); + return true; + }, + test: (error) => error is CancelledException, + ).catchError((Object error) { + print("REALM CHECK: Unhandled exception."); + throw error; + }, test: (error) => error is! CancelledException); + } } Future _addDataToAtlas(App app) async { @@ -1002,7 +984,7 @@ Future _addDataToAtlas(App app) async { realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query1)); } await realm1.subscriptions.waitForSynchronization(); - + final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config2 = Configuration.flexibleSync(user2, [Task.schema]); final realm2 = getRealm(config2); From 0e4adc804b7637946d31559d5675fb78ea5d33fd Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Wed, 12 Oct 2022 17:14:50 +0300 Subject: [PATCH 087/113] Update lib/src/realm_class.dart Co-authored-by: Nikola Irinchev --- lib/src/realm_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index fba789286..af88fe549 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -34,7 +34,7 @@ import 'scheduler.dart'; import 'subscription.dart'; import 'session.dart'; -export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException, CancellableCompleter, CancellableFuture; +export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; export 'package:realm_common/realm_common.dart' show Ignored, From a28eb716876ca06e33074d60d4733fadf9b0786d Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Wed, 12 Oct 2022 19:20:33 +0300 Subject: [PATCH 088/113] Add cancellation_token to realm_core --- 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 ff6199171..aea8924e5 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -24,6 +24,7 @@ import 'dart:io'; import 'dart:typed_data'; // Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead +import 'package:cancellation_token/cancellation_token.dart'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; From 1dfc0d973c7459711151220cfe1a93b1ea87b304 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:04:38 +0300 Subject: [PATCH 089/113] Update test/realm_test.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- test/realm_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 83bf8a308..063ad9bcd 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -921,8 +921,8 @@ Future main([List? args]) async { final isRealm2Cancelled = getRealmAsync(configuration2, cancellationToken: cancellationToken2).thenIsCancelled(); cancellationToken2.cancel(); - await expectLater(await isRealm2Cancelled, isTrue); - await expectLater(await isRealm1Cancelled, isFalse); + expect(await isRealm2Cancelled, isTrue); + expect(await isRealm1Cancelled, isFalse); }); baasTest('Realm.open (flexibleSync) - cancel after realm is returned is no-op', (appConfiguration) async { From 601e9a5f9e5a92dfc2148127379c62079906cbe2 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:04:49 +0300 Subject: [PATCH 090/113] Update test/realm_test.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- test/realm_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 063ad9bcd..9fadc962f 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -992,7 +992,7 @@ Future main([List? args]) async { print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); }).thenIsCancelled(); cancellationToken.cancel(); - await expectLater(await realmIsCancelled, isTrue); + expect(await realmIsCancelled, isTrue); }); } From 8005ebc0bedc9ee64b68dc0000365ab0678faafd Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:05:03 +0300 Subject: [PATCH 091/113] Update test/realm_test.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- test/realm_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 9fadc962f..84bd9e98b 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -887,8 +887,8 @@ Future main([List? args]) async { Future.delayed(Duration(milliseconds: 10), () => cancellationToken.cancel()); final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); - await expectLater(await isRealm1Cancelled, isTrue); - await expectLater(await isRealm2Cancelled, isTrue); + expect(await isRealm1Cancelled, isTrue); + expect(await isRealm2Cancelled, isTrue); }); baasTest('Realm.open (flexibleSync) - open twice the same realm and cancel the first only', (appConfiguration) async { From c4a7a19672938460748dba4d93074a0898ac4d91 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:05:11 +0300 Subject: [PATCH 092/113] Update test/realm_test.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- test/realm_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 84bd9e98b..3851946ef 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -903,8 +903,8 @@ Future main([List? args]) async { var cancellationToken2 = CancellationToken(); final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken2).thenIsCancelled(); cancellationToken1.cancel(); - await expectLater(await isRealm1Cancelled, isTrue); - await expectLater(await isRealm2Cancelled, isFalse); + expect(await isRealm1Cancelled, isTrue); + expect(await isRealm2Cancelled, isFalse); }); baasTest('Realm.open (flexibleSync) - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { From 401e51a2dd71bc13c502bcf4ca64771a67f18fc5 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:05:40 +0300 Subject: [PATCH 093/113] Update test/realm_test.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- test/realm_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 3851946ef..4d5b85493 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -873,7 +873,7 @@ Future main([List? args]) async { var cancellationToken = CancellationToken(); final isRealmCancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); cancellationToken.cancel(); - await expectLater(await isRealmCancelled, isTrue); + expect(await isRealmCancelled, isTrue); }); baasTest('Realm.open (flexibleSync) - open twice the same realm with the same CancelationToken cancels all', (appConfiguration) async { From c14b08259261e7ec43264e79912d9dc80d92c5ed Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 11:06:03 +0300 Subject: [PATCH 094/113] Update lib/src/realm_class.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- lib/src/realm_class.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index af88fe549..6d6aaa0a2 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -711,8 +711,4 @@ class MigrationRealm extends DynamicRealm { typedef ProgressCallback = void Function(SyncProgress syncProgress); /// @nodoc -extension $CancellableCompleterExtension on Completer { - CancellableCompleter asCancellable(CancellationToken? cancellationToken, {OnCancelCallback? onCancel}) { - return CancellableCompleter(cancellationToken, onCancel: onCancel); - } } From ec2b25c52d0d6eb6e878873360193d5f4b0e1243 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Thu, 13 Oct 2022 12:13:51 +0300 Subject: [PATCH 095/113] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- lib/src/realm_class.dart | 34 ++++++++++++++-------------------- test/realm_test.dart | 18 ++++++++---------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 6d6aaa0a2..03361ec8b 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -134,34 +134,28 @@ class Realm implements Finalizable { /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advance. /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open over the constructor. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - if (cancellationToken?.isCancelled ?? false) { - return Future.error(cancellationToken!.exception); - } final realm = Realm(config); - StreamSubscription? subscription; try { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; - if (onProgressCallback != null) { - subscription = session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) - .listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); - } - await session.waitForDownload(cancellationToken); + await Future.wait([ + session.waitForDownload(cancellationToken), + if (onProgressCallback != null) + session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .forEach(onProgressCallback) + .asCancellable(cancellationToken), + ]); } - } catch (error) { + // handle cancellation even if we are not using flexible sync + return await CancellableFuture.value(realm, cancellationToken); + } catch (_) { realm.close(); rethrow; - } finally { - await subscription?.cancel(); } - return realm; } static RealmHandle _openRealmSync(Configuration config) { diff --git a/test/realm_test.dart b/test/realm_test.dart index 4d5b85493..a254da84f 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -1021,21 +1021,19 @@ void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {voi extension _FutureRealm on Future { Future thenIsCancelled() async { - return await then((value) { + try { + final value = await this; expect(value, isNotNull); expect(value.isClosed, false); print("REALM CHECK: Realm returned before cancellation."); return false; - }).onError( - (error, stackTrace) { - print("REALM CHECK: Cancelled before to return the Realm."); - return true; - }, - test: (error) => error is CancelledException, - ).catchError((Object error) { + } on CancelledException { + print("REALM CHECK: Cancelled before to return the Realm."); + return true; + } catch (_) { print("REALM CHECK: Unhandled exception."); - throw error; - }, test: (error) => error is! CancelledException); + rethrow; + } } } From 4d6a656d3809f7387960c994b5bffdac58b58243 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 12:17:09 +0300 Subject: [PATCH 096/113] Code review changes --- lib/src/realm_class.dart | 6 +++--- test/realm_test.dart | 7 +------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 03361ec8b..bb9b8e49c 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -134,6 +134,9 @@ class Realm implements Finalizable { /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advance. /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open over the constructor. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { + if (cancellationToken?.isCancelled ?? false) { + return Future.error(cancellationToken!.exception); + } final realm = Realm(config); try { if (config is FlexibleSyncConfiguration) { @@ -703,6 +706,3 @@ class MigrationRealm extends DynamicRealm { /// * syncProgress - an object of [SyncProgress] that contains `transferredBytes` and `transferableBytes`. /// {@category Realm} typedef ProgressCallback = void Function(SyncProgress syncProgress); - -/// @nodoc -} diff --git a/test/realm_test.dart b/test/realm_test.dart index a254da84f..033bc898b 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -1025,15 +1025,10 @@ extension _FutureRealm on Future { final value = await this; expect(value, isNotNull); expect(value.isClosed, false); - print("REALM CHECK: Realm returned before cancellation."); return false; } on CancelledException { - print("REALM CHECK: Cancelled before to return the Realm."); return true; - } catch (_) { - print("REALM CHECK: Unhandled exception."); - rethrow; - } + } } } From be9d3a2af4e0e53575d7f21604b4ee16f7edeede Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 15:34:08 +0300 Subject: [PATCH 097/113] Code review changes --- lib/src/realm_class.dart | 4 ++-- test/realm_test.dart | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index bb9b8e49c..7f105cdc6 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -134,8 +134,8 @@ class Realm implements Finalizable { /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advance. /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open over the constructor. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - if (cancellationToken?.isCancelled ?? false) { - return Future.error(cancellationToken!.exception); + if (cancellationToken != null && cancellationToken.isCancelled) { + return Future.error(cancellationToken.exception); } final realm = Realm(config); try { diff --git a/test/realm_test.dart b/test/realm_test.dart index 033bc898b..de561102f 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -947,15 +947,14 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - int printCount = 0; + int transferredBytes = 0; var syncedRealm = await getRealmAsync(configuration, onProgressCallback: (syncProgress) { - print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); - printCount++; + transferredBytes = syncProgress.transferredBytes; }); expect(syncedRealm.isClosed, false); - expect(printCount, isNot(0)); + expect(transferredBytes, isNot(0)); }); baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { @@ -972,15 +971,14 @@ Future main([List? args]) async { int printCount = 0; int transferredBytes = 0; - var syncedRealm = await getRealmAsync(config, onProgressCallback: (syncProgress) { - print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + final syncedRealm = await getRealmAsync(config, onProgressCallback: (syncProgress) { printCount++; transferredBytes = syncProgress.transferredBytes; }); expect(syncedRealm.isClosed, false); expect(printCount, isNot(0)); - expect(transferredBytes > 19, isTrue); //19 bytes is the empty realm + expect(transferredBytes, greaterThan(19)); //19 bytes is the empty realm }); baasTest('Realm.open (flexibleSync) - listen and cancel download progress of a populated realm', (appConfiguration) async { From 9e6679028cc7708d69c2d0f2428cd2a9c593a6e4 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 16:10:39 +0300 Subject: [PATCH 098/113] Code review changes --- test/realm_test.dart | 39 ++++++++++++++++++++++----------------- test/test.dart | 9 +++++++++ test/test.g.dart | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index de561102f..c3c36808c 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -788,7 +788,7 @@ Future main([List? args]) async { expect(stored.location, now.location); expect(stored.location.name, 'Europe/Copenhagen'); }); - + test('Realm - open local not encrypted realm with encryption key', () { openEncryptedRealm(null, generateValidKey()); }); @@ -959,9 +959,13 @@ Future main([List? args]) async { baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { final app = App(appConfiguration); - final config = await _addDataToAtlas(app); + final queryDifferentiator = generateRandomString(10); + final itemCount = 200; + final config = await _addDataToAtlas(app, queryDifferentiator: queryDifferentiator, itemCount: itemCount); var syncedRealm = await getRealmAsync(config); expect(syncedRealm.isClosed, false); + final data = syncedRealm.query(r'stringQueryField BEGINSWITH $0', [queryDifferentiator]); + expect(data.length, itemCount); }); baasTest('Realm.open (flexibleSync) - listen for download progress of a populated realm', (appConfiguration) async { @@ -986,13 +990,14 @@ Future main([List? args]) async { final config = await _addDataToAtlas(app); var cancellationToken = CancellationToken(); + bool progressReturned = false; final realmIsCancelled = getRealmAsync(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { - print("PROGRESS: transferredBytes: ${syncProgress.transferredBytes}, totalBytes:${syncProgress.transferableBytes}"); + progressReturned = true; }).thenIsCancelled(); cancellationToken.cancel(); expect(await realmIsCancelled, isTrue); + expect(progressReturned, isFalse); }); - } List generateValidKey() { @@ -1026,37 +1031,37 @@ extension _FutureRealm on Future { return false; } on CancelledException { return true; - } + } } } -Future _addDataToAtlas(App app) async { +Future _addDataToAtlas(App app, {String? queryDifferentiator, int itemCount = 100}) async { + final productNamePrefix = queryDifferentiator ?? generateRandomString(10); final user1 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final config1 = Configuration.flexibleSync(user1, [Task.schema]); + final config1 = Configuration.flexibleSync(user1, [Product.schema]); final realm1 = getRealm(config1); - final query1 = realm1.all(); + final query1 = realm1.query(r'stringQueryField BEGINSWITH $0', [productNamePrefix]); if (realm1.subscriptions.find(query1) == null) { realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query1)); } await realm1.subscriptions.waitForSynchronization(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); - final config2 = Configuration.flexibleSync(user2, [Task.schema]); + final config2 = Configuration.flexibleSync(user2, [Product.schema]); final realm2 = getRealm(config2); - final query2 = realm2.all(); + final query2 = realm2.query(r'stringQueryField BEGINSWITH $0', [productNamePrefix]); if (realm2.subscriptions.find(query2) == null) { realm2.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query2)); } await realm2.subscriptions.waitForSynchronization(); - if (realm2.all().isEmpty) { - realm2.write(() { - for (var i = 0; i < 100; i++) { - realm2.add(Task(ObjectId())); - } - }); - } + realm2.write(() { + for (var i = 0; i < itemCount; i++) { + realm2.add(Product(ObjectId(), "${productNamePrefix}_${i + 1}")); + } + }); await realm2.syncSession.waitForUpload(); + // realm1.close(); realm2.close(); return config1; } diff --git a/test/test.dart b/test/test.dart index 6d3d660a6..265139c21 100644 --- a/test/test.dart +++ b/test/test.dart @@ -97,6 +97,15 @@ class _Task { late ObjectId id; } +@RealmModel() +class _Product { + @PrimaryKey() + @MapTo('_id') + late ObjectId id; + @MapTo('stringQueryField') + late String name; +} + @RealmModel() class _Schedule { @PrimaryKey() diff --git a/test/test.g.dart b/test/test.g.dart index 6085ede6a..7b9e3d540 100644 --- a/test/test.g.dart +++ b/test/test.g.dart @@ -381,6 +381,48 @@ class Task extends _Task with RealmEntity, RealmObject { } } +class Product extends _Product with RealmEntity, RealmObject { + Product( + ObjectId id, + String name, + ) { + RealmObject.set(this, '_id', id); + RealmObject.set(this, 'stringQueryField', name); + } + + Product._(); + + @override + ObjectId get id => RealmObject.get(this, '_id') as ObjectId; + @override + set id(ObjectId value) => RealmObject.set(this, '_id', value); + + @override + String get name => + RealmObject.get(this, 'stringQueryField') as String; + @override + set name(String value) => RealmObject.set(this, 'stringQueryField', value); + + @override + Stream> get changes => + RealmObject.getChanges(this); + + @override + Product freeze() => RealmObject.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(Product._); + return const SchemaObject(Product, 'Product', [ + SchemaProperty('_id', RealmPropertyType.objectid, + mapTo: '_id', primaryKey: true), + SchemaProperty('stringQueryField', RealmPropertyType.string, + mapTo: 'stringQueryField'), + ]); + } +} + class Schedule extends _Schedule with RealmEntity, RealmObject { Schedule( ObjectId id, { From 01e5767406c263075c7250f90788606d8708a131 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 16:17:44 +0300 Subject: [PATCH 099/113] Code review changes --- lib/src/realm_class.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 7f105cdc6..c6121bf8f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -135,8 +135,8 @@ class Realm implements Finalizable { /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open over the constructor. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { if (cancellationToken != null && cancellationToken.isCancelled) { - return Future.error(cancellationToken.exception); - } + throw cancellationToken.exception; + } final realm = Realm(config); try { if (config is FlexibleSyncConfiguration) { From d6138e9421e34d22883127081a08328125318429 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 16:24:12 +0300 Subject: [PATCH 100/113] realm closed after added sunscriptions --- test/realm_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index c3c36808c..6e8073d0d 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -1045,6 +1045,7 @@ Future _addDataToAtlas(App app, {String? queryDifferentiator, int realm1.subscriptions.update((mutableSubscriptions) => mutableSubscriptions.add(query1)); } await realm1.subscriptions.waitForSynchronization(); + realm1.close(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final config2 = Configuration.flexibleSync(user2, [Product.schema]); @@ -1061,7 +1062,6 @@ Future _addDataToAtlas(App app, {String? queryDifferentiator, int } }); await realm2.syncSession.waitForUpload(); - // realm1.close(); realm2.close(); return config1; } From 92f54acaadc139c0d2a0429bf5ed74c133ed2c73 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 18:05:57 +0300 Subject: [PATCH 101/113] Fix test --- test/realm_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/realm_test.dart b/test/realm_test.dart index 6e8073d0d..489f487db 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -947,14 +947,14 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - int transferredBytes = 0; + int transferredBytes = -1; var syncedRealm = await getRealmAsync(configuration, onProgressCallback: (syncProgress) { transferredBytes = syncProgress.transferredBytes; }); expect(syncedRealm.isClosed, false); - expect(transferredBytes, isNot(0)); + expect(transferredBytes, greaterThan(-1)); }); baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { From 99968360260e3959c7d1c86306b00c3f71b68cb9 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Thu, 13 Oct 2022 21:14:06 +0300 Subject: [PATCH 102/113] Return back getProgressStream listen --- lib/src/realm_class.dart | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index c6121bf8f..f82182225 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -138,27 +138,30 @@ class Realm implements Finalizable { throw cancellationToken.exception; } final realm = Realm(config); + StreamSubscription? subscription; try { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; - await Future.wait([ - session.waitForDownload(cancellationToken), - if (onProgressCallback != null) - session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) - .forEach(onProgressCallback) - .asCancellable(cancellationToken), - ]); + if (onProgressCallback != null) { + subscription = session + .getProgressStream( + ProgressDirection.download, + ProgressMode.forCurrentlyOutstandingWork, + ) + .listen( + (syncProgress) => onProgressCallback.call(syncProgress), + cancelOnError: true, + ); + } + await session.waitForDownload(cancellationToken); } - // handle cancellation even if we are not using flexible sync - return await CancellableFuture.value(realm, cancellationToken); } catch (_) { realm.close(); rethrow; + } finally { + await subscription?.cancel(); } + return await CancellableFuture.value(realm, cancellationToken); } static RealmHandle _openRealmSync(Configuration config) { From 62f6f89dde5f6e0152c3b606cb377e7410370f67 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova <95419820+desistefanova@users.noreply.github.com> Date: Fri, 14 Oct 2022 10:43:39 +0300 Subject: [PATCH 103/113] Update lib/src/realm_class.dart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kasper Overgård Nielsen --- lib/src/realm_class.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index f82182225..d7299fed3 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -148,10 +148,7 @@ class Realm implements Finalizable { ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork, ) - .listen( - (syncProgress) => onProgressCallback.call(syncProgress), - cancelOnError: true, - ); + .listen(onProgressCallback); } await session.waitForDownload(cancellationToken); } From 12f315ebd89036bdbb1e54a1a7a4a58889474405 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 14 Oct 2022 10:55:59 +0300 Subject: [PATCH 104/113] Cancel subscription before realm close --- lib/src/realm_class.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index d7299fed3..a996dfb6e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -146,17 +146,16 @@ class Realm implements Finalizable { subscription = session .getProgressStream( ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork, - ) + ProgressMode.forCurrentlyOutstandingWork) .listen(onProgressCallback); } await session.waitForDownload(cancellationToken); + await subscription?.cancel(); } } catch (_) { + await subscription?.cancel(); realm.close(); rethrow; - } finally { - await subscription?.cancel(); } return await CancellableFuture.value(realm, cancellationToken); } From 388e623f951130ca5aa38d83fc512f6af36eecc3 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Fri, 14 Oct 2022 11:02:04 +0300 Subject: [PATCH 105/113] Fixed test with query subscription --- test/subscription_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/subscription_test.dart b/test/subscription_test.dart index 5fa3303ea..4ae057f19 100644 --- a/test/subscription_test.dart +++ b/test/subscription_test.dart @@ -545,6 +545,7 @@ Future main([List? args]) async { realm.subscriptions.update((mutableSubscriptions) { mutableSubscriptions.add(realm.all()); }); + await realm.subscriptions.waitForSynchronization(); realm.write(() { realm.addAll([ @@ -563,6 +564,7 @@ Future main([List? args]) async { }); await realm.subscriptions.waitForSynchronization(); + await realm.syncSession.waitForDownload(); var filtered = realm.query(realm.subscriptions.findByName("filter")!.queryString); var all = realm.all(); From 05ec2929cfc51cdd0561db7c418692b0c34a1adb Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 17 Oct 2022 23:33:26 +0300 Subject: [PATCH 106/113] Merge from master --- .github/workflows/auto-assign.yml | 4 +- .github/workflows/binary-combine-android.yml | 12 +- .github/workflows/binary-combine-ios.yml | 10 +- .github/workflows/build-native.yml | 6 +- .github/workflows/check-changelog.yml | 2 +- .github/workflows/ci.yml | 18 +- .github/workflows/cleanup-clusters.yml | 1 + .github/workflows/dart-desktop-tests.yml | 6 +- .github/workflows/flutter-desktop-tests.yml | 6 +- .github/workflows/prepare-release.yml | 4 +- .github/workflows/publish-release.yml | 9 +- .github/workflows/update-samples.yml | 24 ++ CHANGELOG.md | 9 +- lib/src/cli/generate/generate_command.dart | 10 +- lib/src/configuration.dart | 2 +- lib/src/native/realm_core.dart | 110 ++++++++ lib/src/realm_class.dart | 95 ++++++- test/configuration_test.dart | 11 +- test/realm_test.dart | 280 ++++++++++++++++++- 19 files changed, 548 insertions(+), 71 deletions(-) create mode 100644 .github/workflows/update-samples.yml diff --git a/.github/workflows/auto-assign.yml b/.github/workflows/auto-assign.yml index 2be2a53e3..3db84b345 100644 --- a/.github/workflows/auto-assign.yml +++ b/.github/workflows/auto-assign.yml @@ -4,7 +4,7 @@ on: types: [opened] jobs: - add-reviews: + add-assignee: runs-on: ubuntu-latest steps: - - uses: kentaro-m/auto-assign-action@f4749f4f975e07ce2ee405c4508f11e9901b7b43 + - uses: kentaro-m/auto-assign-action@248761c4feb3917c1b0444e33fad1a50093b9847 diff --git a/.github/workflows/binary-combine-android.yml b/.github/workflows/binary-combine-android.yml index d8d32ad49..d664defcf 100644 --- a/.github/workflows/binary-combine-android.yml +++ b/.github/workflows/binary-combine-android.yml @@ -9,35 +9,35 @@ jobs: runs-on: ubuntu-latest steps: - name: Fetch x86 build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-android-x86 path: binary/android - name: Fetch x86_64 build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-android-x86_64 path: binary/android - name: Fetch armeabi-v7a build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-android-armeabi-v7a path: binary/android - name: Fetch arm64-v8a build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-android-arm64-v8a path: binary/android - name: Store combined artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 with: name: librealm-android path: binary/android retention-days: 1 - name: Delete individual build artifacts - uses: geekyeggo/delete-artifact@v1 + uses: geekyeggo/delete-artifact@d5464431b27e231287c52139bde3f3c4e32c5622 with: name: | librealm-android-x86 diff --git a/.github/workflows/binary-combine-ios.yml b/.github/workflows/binary-combine-ios.yml index 0bee2505c..19ea1789b 100644 --- a/.github/workflows/binary-combine-ios.yml +++ b/.github/workflows/binary-combine-ios.yml @@ -10,17 +10,17 @@ jobs: steps: - name: Fetch device build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-ios-device path: binary/ios - name: Fetch simulator build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-ios-simulator path: binary/ios - name: Fetch catalyst build - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-ios-catalyst path: binary/ios @@ -35,14 +35,14 @@ jobs: rm -rf ./binary/ios/Release-* - name: Store .xcframework artifact - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 with: name: librealm-ios path: binary/ios retention-days: 1 - name: Delete individual framework artifacts - uses: geekyeggo/delete-artifact@v1 + uses: geekyeggo/delete-artifact@d5464431b27e231287c52139bde3f3c4e32c5622 with: name: | librealm-ios-device diff --git a/.github/workflows/build-native.yml b/.github/workflows/build-native.yml index e8cd80bc2..6f3ef7630 100644 --- a/.github/workflows/build-native.yml +++ b/.github/workflows/build-native.yml @@ -26,7 +26,7 @@ jobs: build: ${{ fromJSON(inputs.build) }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: 'recursive' @@ -40,7 +40,7 @@ jobs: - name: Setup Ninja if: contains(github.head_ref, 'release/') || steps.check-cache.outputs.cache-hit != 'true' - uses: seanmiddleditch/gha-setup-ninja@master + uses: seanmiddleditch/gha-setup-ninja@1815f2d05c2cd60c2d900f89843139b8dde09f4c - name: Get vcpkg submodule commit sha id: vcpkg_cache_key @@ -73,7 +73,7 @@ jobs: cmake --build --preset ${{ matrix.build }} --config Release - name: Store artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 with: name: librealm-${{ matrix.build }} path: binary/${{ inputs.binary }}/** diff --git a/.github/workflows/check-changelog.yml b/.github/workflows/check-changelog.yml index 625c76426..6b5d8d4a6 100644 --- a/.github/workflows/check-changelog.yml +++ b/.github/workflows/check-changelog.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false - name: Enforce Changelog diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9237c3ee2..a90b00044 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,7 +106,7 @@ jobs: BAAS_DIFFERENTIATOR: ${{ matrix.app }}${{ github.run_id }}${{ github.run_attempt }} steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false @@ -276,7 +276,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: 'recursive' @@ -284,7 +284,7 @@ jobs: run: echo "PATH=/usr/lib/ccache:/usr/local/opt/ccache/libexec:$PATH" >> $GITHUB_ENV - name: Fetch artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-ios path: binary/ios @@ -298,7 +298,7 @@ jobs: run: flutter pub get - name: Launch Simulator - uses: futureware-tech/simulator-action@v1 + uses: futureware-tech/simulator-action@v2 with: model: 'iPhone 8' os: 'iOS' @@ -334,12 +334,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: 'recursive' - name: Fetch artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-android path: binary/android @@ -413,7 +413,7 @@ jobs: name: Generator Tests steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: 'recursive' @@ -481,7 +481,7 @@ jobs: - name: Publish Generator Coverage if: matrix.os == 'ubuntu' id: publish-coverage - uses: coverallsapp/github-action@1.1.3 + uses: coverallsapp/github-action@a1a1a8a300a7e89df3630639df8fb23de5cc6368 with: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: generator @@ -501,7 +501,7 @@ jobs: - name: Coveralls Finished id: publish-coverage - uses: coverallsapp/github-action@1.1.3 + uses: coverallsapp/github-action@a1a1a8a300a7e89df3630639df8fb23de5cc6368 with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true diff --git a/.github/workflows/cleanup-clusters.yml b/.github/workflows/cleanup-clusters.yml index 2bd72bd0f..353584db3 100644 --- a/.github/workflows/cleanup-clusters.yml +++ b/.github/workflows/cleanup-clusters.yml @@ -14,3 +14,4 @@ jobs: projectId: ${{ secrets.ATLAS_QA_PROJECT_ID }} apiKey: ${{ secrets.ATLAS_QA_PUBLIC_API_KEY }} privateApiKey: ${{ secrets.ATLAS_QA_PRIVATE_API_KEY }} + diff --git a/.github/workflows/dart-desktop-tests.yml b/.github/workflows/dart-desktop-tests.yml index 7f82cfa08..db376ec4e 100644 --- a/.github/workflows/dart-desktop-tests.yml +++ b/.github/workflows/dart-desktop-tests.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false @@ -45,7 +45,7 @@ jobs: run: git clean -fdx - name: Fetch artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-${{ inputs.os }} path: binary/${{ inputs.os }} @@ -92,7 +92,7 @@ jobs: - name: Publish realm_dart coverage if: inputs.os == 'linux' id: publish-coverage - uses: coverallsapp/github-action@1.1.3 + uses: coverallsapp/github-action@a1a1a8a300a7e89df3630639df8fb23de5cc6368 with: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: realm_dart diff --git a/.github/workflows/flutter-desktop-tests.yml b/.github/workflows/flutter-desktop-tests.yml index b8e4880bd..f4ff224b5 100644 --- a/.github/workflows/flutter-desktop-tests.yml +++ b/.github/workflows/flutter-desktop-tests.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false @@ -49,10 +49,10 @@ jobs: - name: Setup Ninja if: ${{ inputs.os == 'linux' }} - uses: seanmiddleditch/gha-setup-ninja@master + uses: seanmiddleditch/gha-setup-ninja@1815f2d05c2cd60c2d900f89843139b8dde09f4c - name: Fetch artifacts - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: librealm-${{ inputs.os }} path: binary/${{ inputs.os }} diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml index 5f79af785..dd23d2c3d 100644 --- a/.github/workflows/prepare-release.yml +++ b/.github/workflows/prepare-release.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout Code - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false @@ -71,5 +71,5 @@ jobs: title: '[Release ${{ steps.update-changelog.outputs.new-version }}]' draft: false body: An automated PR for next release. - commit-message: Prepare for ${{ steps.update-changelog.outputs.new-version }} + commit-message: '[Release ${{ steps.update-changelog.outputs.new-version }}]' token: ${{ secrets.REALM_CI_PAT }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index f01b5c112..567fbaf67 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -18,7 +18,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false @@ -135,7 +135,7 @@ jobs: shell: pwsh - name: Upload release folder - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 with: name: release-bundle path: release/** @@ -170,12 +170,12 @@ jobs: - prepare-packages steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@8230315d06ad95c617244d2f265d237a1682d445 with: submodules: false - name: Download release folder - uses: actions/download-artifact@v2 + uses: actions/download-artifact@a327a9c763239d85dc3d78908dbf6ca74acd2e46 with: name: release-bundle path: release @@ -222,6 +222,7 @@ jobs: - name: 'Post to #realm-releases' uses: realm/ci-actions/release-to-slack@v3 + continue-on-error: true with: changelog: release/ExtractedChangelog.md sdk: Flutter/Dart diff --git a/.github/workflows/update-samples.yml b/.github/workflows/update-samples.yml new file mode 100644 index 000000000..8d93f045c --- /dev/null +++ b/.github/workflows/update-samples.yml @@ -0,0 +1,24 @@ +name: Update samples release version +on: + release: + types: [published] + workflow_dispatch: + +jobs: + update-samples-version: + runs-on: ubuntu-latest + steps: + - name: 'Get release version' + id: release-version + uses: pozetroninc/github-action-get-latest-release@06da55dc399d06375d2d1fe57542398d5bd989c6 + with: + repository: ${{ github.repository }} + + - name: Trigger update samples CI + run: | + curl \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.REALM_CI_PAT }}" \ + https://api.github.com/repos/realm/realm-dart-samples/actions/workflows/37091855/dispatches \ + -d '{"ref":"master","inputs":{"version":"${{steps.release-version.outputs.release }}"}' \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index eae1296bb..da41138cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,16 +3,21 @@ **This project is in the Beta stage. The API should be quite stable, but occasional breaking changes may be made.** ### Enhancements -* Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) +* Added support for asynchronous transactions. (Issue [#802](https://github.com/realm/realm-dart/issues/802)) + * Added `Transaction` which is a class that exposes an API for committing and rolling back an active transaction. + * Added `realm.beginWriteAsync` which returns a `Future` that resolves when the write lock has been obtained. + * Added `realm.writeAsync` which opens an asynchronous transaction, invokes the provided callback, then commits the transaction asynchronously. + * Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Added more validations when using `User.apiKeys` to return more meaningful errors when the user cannot perform API key actions - e.g. when the user has been logged in with API key credentials or when the user has been logged out. (Issue [#950](https://github.com/realm/realm-dart/issues/950)) +* Fixed `dart run realm_dart generate` and `flutter pub run realm generate` commands to exit with the correct error code on failure. ### Compatibility * Realm Studio: 12.0.0 or later. ### Internal -* Using Core 12.9.0. +* Using Core 12.9.0 ## 0.5.0+beta (2022-10-10) diff --git a/lib/src/cli/generate/generate_command.dart b/lib/src/cli/generate/generate_command.dart index 372f9317c..0ec86fe6d 100644 --- a/lib/src/cli/generate/generate_command.dart +++ b/lib/src/cli/generate/generate_command.dart @@ -42,10 +42,16 @@ class GenerateCommand extends Command { 'run', 'build_runner', // prioritize clean, then watch, then build - options.clean ? 'clean' : options.watch ? 'watch' : 'build', + options.clean + ? 'clean' + : options.watch + ? 'watch' + : 'build', ...[if (!options.clean) '--delete-conflicting-outputs'], // not legal option to clean ]); - + await stdout.addStream(process.stdout); + final exitCode = await process.exitCode; + exit(exitCode); } } diff --git a/lib/src/configuration.dart b/lib/src/configuration.dart index 9ef964f1d..141f0df6d 100644 --- a/lib/src/configuration.dart +++ b/lib/src/configuration.dart @@ -19,13 +19,13 @@ import 'dart:ffi'; import 'dart:io'; +// ignore: no_leading_underscores_for_library_prefixes import 'package:path/path.dart' as _path; import 'native/realm_core.dart'; import 'realm_class.dart'; import 'init.dart'; import 'user.dart'; -import 'migration.dart'; /// The signature of a callback used to determine if compaction /// should be attempted. diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index aea8924e5..7682a5586 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -23,6 +23,7 @@ import 'dart:ffi'; import 'dart:io'; import 'dart:typed_data'; +import 'package:cancellation_token/cancellation_token.dart'; // Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead import 'package:cancellation_token/cancellation_token.dart'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; @@ -583,6 +584,56 @@ class _RealmCore { _realmLib.invokeGetBool(() => _realmLib.realm_commit(realm.handle._pointer), "Could not commit write"); } + Future beginWriteAsync(Realm realm, CancellationToken? ct) { + final completer = WriteCompleter(realm, ct); + if (!completer.isCancelled) { + completer.id = _realmLib.realm_async_begin_write(realm.handle._pointer, Pointer.fromFunction(_completeAsyncBeginWrite), completer.toPersistentHandle(), + _realmLib.addresses.realm_dart_delete_persistent_handle, true); + } + + return completer.future; + } + + Future commitWriteAsync(Realm realm, CancellationToken? ct) { + final completer = WriteCompleter(realm, ct); + if (!completer.isCancelled) { + completer.id = _realmLib.realm_async_commit(realm.handle._pointer, Pointer.fromFunction(_completeAsyncCommit), completer.toPersistentHandle(), + _realmLib.addresses.realm_dart_delete_persistent_handle, false); + } + + return completer.future; + } + + bool _cancelAsync(Realm realm, int cancelationId) { + return using((Arena arena) { + final didCancel = arena(); + _realmLib.invokeGetBool(() => _realmLib.realm_async_cancel(realm.handle._pointer, cancelationId, didCancel)); + return didCancel.value; + }); + } + + static void _completeAsyncBeginWrite(Pointer userdata) { + Completer? completer = userdata.toObject(isPersistent: true); + if (completer == null) { + return; + } + + completer.complete(); + } + + static void _completeAsyncCommit(Pointer userdata, bool error, Pointer description) { + Completer? completer = userdata.toObject(isPersistent: true); + if (completer == null) { + return; + } + + if (error) { + completer.completeError(RealmException(description.cast().toDartString())); + } else { + completer.complete(); + } + } + bool getIsWritable(Realm realm) { return _realmLib.realm_is_writable(realm.handle._pointer); } @@ -2649,6 +2700,65 @@ extension LevelExt on Level { } } +class WriteCompleter with Cancellable implements Completer { + final Completer _internalCompleter; + final CancellationToken? _cancellationToken; + int? _id; + final Realm _realm; + + set id(int value) { + if (_id != null) { + throw RealmError('id should only be set once'); + } + + _id = value; + } + + WriteCompleter(this._realm, this._cancellationToken) : _internalCompleter = Completer() { + final ct = _cancellationToken; + if (ct != null) { + if (ct.isCancelled) { + _internalCompleter.completeError(ct.exception); + } else { + ct.attach(this); + } + } + } + + /// Whether or not the completer was cancelled. + bool get isCancelled => _cancellationToken?.isCancelled ?? false; + + @override + bool get isCompleted => _internalCompleter.isCompleted; + + @override + Future get future => _internalCompleter.future; + + @override + void complete([FutureOr? value]) { + if (isCancelled) return; + _cancellationToken?.detach(this); + _internalCompleter.complete(value); + } + + @override + void completeError(Object error, [StackTrace? stackTrace]) { + if (isCancelled) return; + _cancellationToken?.detach(this); + _internalCompleter.completeError(error, stackTrace); + } + + @override + void onCancel(Exception cancelException, [StackTrace? stackTrace]) { + if (_id == null || realmCore._cancelAsync(_realm, _id!)) { + _internalCompleter.completeError( + cancelException, + stackTrace ?? StackTrace.current, + ); + } + } +} + extension PlatformEx on Platform { static String fromEnvironment(String name, {String defaultValue = ""}) { final result = Platform.environment[name]; diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index a996dfb6e..456048f7a 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -20,6 +20,7 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; +import 'package:cancellation_token/cancellation_token.dart'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:collection/collection.dart'; @@ -62,6 +63,8 @@ export 'package:realm_common/realm_common.dart' ObjectId, Uuid; +export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; + // always expose with `show` to explicitly control the public API surface export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App, AppException; export "configuration.dart" @@ -280,7 +283,7 @@ class Realm implements Finalizable { /// If no exception is thrown from within the callback, the transaction will be committed. /// It is more efficient to update several properties or even create multiple objects in a single write transaction. T write(T Function() writeCallback) { - final transaction = Transaction._(this); + final transaction = beginWrite(); try { T result = writeCallback(); @@ -292,6 +295,42 @@ class Realm implements Finalizable { } } + /// Begins a write transaction for this [Realm]. + Transaction beginWrite() { + _ensureWritable(); + + realmCore.beginWrite(this); + + return Transaction._(this); + } + + /// Asynchronously begins a write transaction for this [Realm]. You can supply a + /// [CancellationToken] to cancel the operation. + Future beginWriteAsync([CancellationToken? cancellationToken]) async { + _ensureWritable(); + + await realmCore.beginWriteAsync(this, cancellationToken); + + return Transaction._(this); + } + + /// Executes the provided [writeCallback] in a temporary write transaction. Both acquiring the write + /// lock and committing the transaction will be done asynchronously. + Future writeAsync(T Function() writeCallback, [CancellationToken? cancellationToken]) async { + final transaction = await beginWriteAsync(cancellationToken); + + try { + T result = writeCallback(); + await transaction.commitAsync(cancellationToken); + return result; + } catch (e) { + if (isInTransaction) { + transaction.rollback(); + } + rethrow; + } + } + /// Closes the `Realm`. /// /// All [RealmObject]s and `Realm ` collections are invalidated and can not be used. @@ -419,32 +458,66 @@ class Realm implements Finalizable { /// Disclaimer: This method is mostly needed on Dart standalone and if not called the Dart program 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(); + + void _ensureWritable() { + if (isFrozen) { + throw RealmError('Starting a write transaction on a frozen Realm is not allowed.'); + } + } } -/// @nodoc +/// Provides a scope to safely write data to a [Realm]. Can be created using [Realm.beginWrite] or +/// [Realm.beginWriteAsync]. class Transaction { Realm? _realm; + /// Returns whether the transaction is still active. + bool get isOpen => _realm != null; + Transaction._(Realm realm) { _realm = realm; - realmCore.beginWrite(realm); } + /// Commits the changes to the Realm. void commit() { - if (_realm == null) { - throw RealmException('Transaction was already closed. Cannot commit'); - } + final realm = _ensureOpen('commit'); - realmCore.commitWrite(_realm!); - _realm = null; + realmCore.commitWrite(realm); + + _closeTransaction(); + } + + /// Commits the changes to the Realm asynchronously. + /// Canceling the commit using the [cancellationToken] will not abort the transaction, but + /// rather resolve the future immediately with a [CancelledException]. + Future commitAsync([CancellationToken? cancellationToken]) async { + final realm = _ensureOpen('commitAsync'); + + await realmCore.commitWriteAsync(realm, cancellationToken); + + _closeTransaction(); } + /// Undoes all changes made in the transaction. void rollback() { - if (_realm == null) { - throw RealmException('Transaction was already closed. Cannot rollback'); + final realm = _ensureOpen('rollback'); + + if (!realm.isClosed) { + realmCore.rollbackWrite(realm); + } + + _closeTransaction(); + } + + Realm _ensureOpen(String action) { + if (!isOpen) { + throw RealmException('Transaction was already closed. Cannot $action'); } - realmCore.rollbackWrite(_realm!); + return _realm!; + } + + void _closeTransaction() { _realm = null; } } diff --git a/test/configuration_test.dart b/test/configuration_test.dart index 2fc3081ec..44d1a05a8 100644 --- a/test/configuration_test.dart +++ b/test/configuration_test.dart @@ -555,7 +555,7 @@ Future main([List? args]) async { expect(disconnectedRealm.find(oid), isNotNull); }); - test('Configuration set short encryption key', () { + test('Configuration.local set too short encryption key size', () { List key = [1, 2, 3]; expect( () => Configuration.local([Car.schema], encryptionKey: key), @@ -563,7 +563,7 @@ Future main([List? args]) async { ); }); - test('Configuration set byte exceeding encryption key', () { + test('Configuration set encryption key not a list of bytes', () { List byteExceedingKey = List.generate(encryptionKeySize, (i) => random.nextInt(4294967296)); expect( () => Configuration.local([Car.schema], encryptionKey: byteExceedingKey), @@ -571,12 +571,13 @@ Future main([List? args]) async { ); }); - test('Configuration set a correct encryption key', () { + test('Configuration set a valid encryption key', () { List key = List.generate(encryptionKeySize, (i) => random.nextInt(256)); - Configuration.local([Car.schema], encryptionKey: key); + final config = Configuration.local([Car.schema], encryptionKey: key); + expect(config.encryptionKey, key); }); - baasTest('FlexibleSyncConfiguration set long encryption key', (appConfiguration) async { + baasTest('FlexibleSyncConfiguration set too long encryption key size', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); diff --git a/test/realm_test.dart b/test/realm_test.dart index 489f487db..d7a28fb2c 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -18,11 +18,14 @@ // ignore_for_file: unused_local_variable, avoid_relative_lib_imports +import 'dart:convert'; import 'dart:io'; +import 'package:cancellation_token/cancellation_token.dart'; import 'package:test/test.dart' hide test, throws; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/data/latest.dart' as tz; import '../lib/realm.dart'; +import 'package:path/path.dart' as p; import 'test.dart'; @@ -720,7 +723,31 @@ Future main([List? args]) async { final realm = getRealm(config); final frozenRealm = freezeRealm(realm); - expect(() => frozenRealm.write(() {}), throws("Can't perform transactions on a frozen Realm")); + expect(() => frozenRealm.write(() {}), throws("Starting a write transaction on a frozen Realm is not allowed.")); + }); + + test('FrozenRealm cannot beginWrite', () { + final config = Configuration.local([Person.schema]); + final realm = getRealm(config); + + final frozenRealm = freezeRealm(realm); + expect(() => frozenRealm.beginWrite(), throws("Starting a write transaction on a frozen Realm is not allowed.")); + }); + + test('FrozenRealm cannot writeAsync', () async { + final config = Configuration.local([Person.schema]); + final realm = getRealm(config); + + final frozenRealm = freezeRealm(realm); + await expectLater(() => frozenRealm.writeAsync(() {}), throws("Starting a write transaction on a frozen Realm is not allowed.")); + }); + + test('FrozenRealm cannot beginWriteAsync', () async { + final config = Configuration.local([Person.schema]); + final realm = getRealm(config); + + final frozenRealm = freezeRealm(realm); + await expectLater(() => frozenRealm.beginWriteAsync(), throws("Starting a write transaction on a frozen Realm is not allowed.")); }); test('realm.freeze when frozen returns the same instance', () { @@ -789,33 +816,49 @@ Future main([List? args]) async { expect(stored.location.name, 'Europe/Copenhagen'); }); - test('Realm - open local not encrypted realm with encryption key', () { - openEncryptedRealm(null, generateValidKey()); + test('Realm - encryption works', () { + var config = Configuration.local([Friend.schema], path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); + var realm = getRealm(config); + readFile(String path) { + final bytes = File(path).readAsBytesSync(); + return utf8.decode(bytes, allowMalformed: true); + } + var decoded = readFile(realm.config.path); + expect(decoded, contains("bestFriend")); + + config = Configuration.local([Friend.schema], encryptionKey: generateEncryptionKey(), path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); + realm = getRealm(config); + decoded = readFile(realm.config.path); + expect(decoded, isNot(contains("bestFriend"))); + }); + + test('Realm - open local not encrypted realm with an encryption key', () { + openEncryptedRealm(null, generateEncryptionKey()); }); test('Realm - open local encrypted realm with an empty encryption key', () { - openEncryptedRealm(generateValidKey(), null); + openEncryptedRealm(generateEncryptionKey(), null); }); test('Realm - open local encrypted realm with an invalid encryption key', () { - openEncryptedRealm(generateValidKey(), generateValidKey()); + openEncryptedRealm(generateEncryptionKey(), generateEncryptionKey()); }); test('Realm - open local encrypted realm with the correct encryption key', () { - List key = generateValidKey(); + List key = generateEncryptionKey(); openEncryptedRealm(key, key); }); - test('Realm - open closed local encrypted realm with the correct encryption key', () { - List key = generateValidKey(); + test('Realm - open existing local encrypted realm with the correct encryption key', () { + List key = generateEncryptionKey(); openEncryptedRealm(key, key, afterEncrypt: (realm) => realm.close()); }); - test('Realm - open closed local encrypted realm with an invalid encryption key', () { - openEncryptedRealm(generateValidKey(), generateValidKey(), afterEncrypt: (realm) => realm.close()); + test('Realm - open existing local encrypted realm with an invalid encryption key', () { + openEncryptedRealm(generateEncryptionKey(), generateEncryptionKey(), afterEncrypt: (realm) => realm.close()); }); - baasTest('Realm - open remote encrypted realm with encryption key', (appConfiguration) async { + baasTest('Realm - open synced encrypted realm with encryption key', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -830,6 +873,219 @@ Future main([List? args]) async { ); }); + test('Realm.beginWriteAsync starts write transaction', () async { + final realm = getRealm(Configuration.local([Person.schema])); + final transaction = await realm.beginWriteAsync(); + + expect(transaction.isOpen, true); + }); + + test('Realm.beginWriteAsync with sync commit persists changes', () async { + final realm = getRealm(Configuration.local([Person.schema])); + final transaction = await realm.beginWriteAsync(); + realm.add(Person('John')); + transaction.commit(); + + expect(realm.all().length, 1); + }); + + test('Realm.beginWriteAsync with async commit persists changes', () async { + final realm = getRealm(Configuration.local([Person.schema])); + final transaction = await realm.beginWriteAsync(); + realm.add(Person('John')); + await transaction.commitAsync(); + + expect(realm.all().length, 1); + }); + + test('Realm.beginWrite with sync commit persists changes', () { + final realm = getRealm(Configuration.local([Person.schema])); + final transaction = realm.beginWrite(); + realm.add(Person('John')); + transaction.commit(); + + expect(realm.all().length, 1); + }); + + test('Realm.beginWrite with async commit persists changes', () async { + final realm = getRealm(Configuration.local([Person.schema])); + final transaction = realm.beginWrite(); + realm.add(Person('John')); + await transaction.commitAsync(); + + expect(realm.all().length, 1); + }); + + test('Realm.beginWriteAsync rollback undoes changes', () async { + final realm = getRealm(Configuration.local([Person.schema])); + final transaction = await realm.beginWriteAsync(); + realm.add(Person('John')); + transaction.rollback(); + + expect(realm.all().length, 0); + }); + + test('Realm.writeAsync allows persists changes', () async { + final realm = getRealm(Configuration.local([Person.schema])); + await realm.writeAsync(() { + realm.add(Person('John')); + }); + + expect(realm.all().length, 1); + }); + + test('Realm.beginWriteAsync when realm is closed undoes changes', () async { + final realm1 = getRealm(Configuration.local([Person.schema])); + final realm2 = getRealm(Configuration.local([Person.schema])); + + await realm1.beginWriteAsync(); + realm1.add(Person('John')); + realm1.close(); + + expect(realm2.all().length, 0); + }); + + test("Realm.writeAsync with multiple transactions doesnt't deadlock", () async { + final realm = getRealm(Configuration.local([Person.schema])); + final t1 = await realm.beginWriteAsync(); + realm.add(Person('Marco')); + + final writeFuture = realm.writeAsync(() { + realm.add(Person('Giovanni')); + }); + + await t1.commitAsync(); + await writeFuture; + + final people = realm.all(); + expect(people.length, 2); + expect(people[0].name, 'Marco'); + expect(people[1].name, 'Giovanni'); + }); + + test('Realm.writeAsync returns valid objects', () async { + final realm = getRealm(Configuration.local([Person.schema])); + final person = await realm.writeAsync(() { + return realm.add(Person('John')); + }); + + expect(person.name, 'John'); + }); + + test('Realm.writeAsync throws user exception', () async { + final realm = getRealm(Configuration.local([Person.schema])); + try { + await realm.writeAsync(() { + throw Exception('User exception'); + }); + } on Exception catch (e) { + expect(e.toString(), 'Exception: User exception'); + } + }); + + test('Realm.writeAsync FIFO order ensured', () async { + final acquisitionOrder = []; + final futures = >[]; + + final realm = getRealm(Configuration.local([Person.schema])); + + for (var i = 0; i < 5; i++) { + futures.add(realm.writeAsync(() { + acquisitionOrder.add(i); + })); + } + + await Future.wait(futures); + + expect(acquisitionOrder, [0, 1, 2, 3, 4]); + }); + + test('Realm.beginWriteAsync with cancellation token', () async { + final realm1 = getRealm(Configuration.local([Person.schema])); + final realm2 = getRealm(Configuration.local([Person.schema])); + final t1 = realm1.beginWrite(); + + final token = TimeoutCancellationToken(const Duration(milliseconds: 1), timeoutException: CancelledException()); + + await expectLater(realm2.beginWriteAsync(token), throwsA(isA())); + + t1.rollback(); + }); + + test('Realm.writeAsync with cancellation token', () async { + final realm1 = getRealm(Configuration.local([Person.schema])); + final realm2 = getRealm(Configuration.local([Person.schema])); + final t1 = realm1.beginWrite(); + + final token = CancellationToken(); + Future.delayed(Duration(milliseconds: 1)).then((value) => token.cancel()); + + await expectLater(realm2.writeAsync(() {}, token), throwsA(isA())); + + t1.rollback(); + }); + + test('Realm.beginWriteAsync when canceled after write lock obtained is a no-op', () async { + final realm = getRealm(Configuration.local([Person.schema])); + + final token = CancellationToken(); + final transaction = await realm.beginWriteAsync(token); + token.cancel(); + + expect(transaction.isOpen, true); + expect(realm.isInTransaction, true); + + transaction.rollback(); + }); + + test('Realm.writeAsync when canceled after write lock obtained rolls it back', () async { + final realm = getRealm(Configuration.local([Person.schema])); + + final token = CancellationToken(); + await expectLater( + realm.writeAsync(() { + realm.add(Person('A')); + token.cancel(); + realm.add(Person('B')); + }, token), + throwsA(isA())); + + expect(realm.all().length, 0); + expect(realm.isInTransaction, false); + }); + + test('Realm.writeAsync with a canceled token throws', () async { + final realm = getRealm(Configuration.local([Person.schema])); + + final token = CancellationToken(); + token.cancel(); + + await expectLater(realm.writeAsync(() {}, token), throwsA(isA())); + expect(realm.isInTransaction, false); + }); + + test('Realm.beginWriteAsync with a canceled token throws', () async { + final realm = getRealm(Configuration.local([Person.schema])); + + final token = CancellationToken(); + token.cancel(); + + await expectLater(realm.beginWriteAsync(token), throwsA(isA())); + expect(realm.isInTransaction, false); + }); + + test('Transaction.commitAsync with a canceled token throws', () async { + final realm = getRealm(Configuration.local([Person.schema])); + + final transaction = await realm.beginWriteAsync(); + + final token = CancellationToken(); + token.cancel(); + + await expectLater(transaction.commitAsync(token), throwsA(isA())); + expect(realm.isInTransaction, true); + }); + baasTest('Realm.open (flexibleSync)', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -1000,7 +1256,7 @@ Future main([List? args]) async { }); } -List generateValidKey() { +List generateEncryptionKey() { return List.generate(encryptionKeySize, (i) => random.nextInt(256)); } From c774464fb1613b97e81c160c18724927e1971c27 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Mon, 17 Oct 2022 23:35:21 +0300 Subject: [PATCH 107/113] Update changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index da41138cb..d01ff1e1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Added `Transaction` which is a class that exposes an API for committing and rolling back an active transaction. * Added `realm.beginWriteAsync` which returns a `Future` that resolves when the write lock has been obtained. * Added `realm.writeAsync` which opens an asynchronous transaction, invokes the provided callback, then commits the transaction asynchronously. - * Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) +* Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Added more validations when using `User.apiKeys` to return more meaningful errors when the user cannot perform API key actions - e.g. when the user has been logged in with API key credentials or when the user has been logged out. (Issue [#950](https://github.com/realm/realm-dart/issues/950)) From eb85d16674fd9d0fe583d669e798e1c526dacc11 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 18 Oct 2022 09:28:03 +0300 Subject: [PATCH 108/113] Regenerate metrics.g.dart --- flutter/realm_flutter/example/lib/main.g.dart | 116 ------------------ lib/src/cli/metrics/metrics.g.dart | 4 +- 2 files changed, 2 insertions(+), 118 deletions(-) delete mode 100644 flutter/realm_flutter/example/lib/main.g.dart diff --git a/flutter/realm_flutter/example/lib/main.g.dart b/flutter/realm_flutter/example/lib/main.g.dart deleted file mode 100644 index 96e747646..000000000 --- a/flutter/realm_flutter/example/lib/main.g.dart +++ /dev/null @@ -1,116 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'main.dart'; - -// ************************************************************************** -// RealmObjectGenerator -// ************************************************************************** - -class Car extends _Car with RealmEntity, RealmObject { - static var _defaultsSet = false; - - Car( - String make, { - String? model, - int? kilometers = 500, - Person? owner, - }) { - if (!_defaultsSet) { - _defaultsSet = RealmObject.setDefaults({ - 'kilometers': 500, - }); - } - RealmObject.set(this, 'make', make); - RealmObject.set(this, 'model', model); - RealmObject.set(this, 'kilometers', kilometers); - RealmObject.set(this, 'owner', owner); - } - - Car._(); - - @override - String get make => RealmObject.get(this, 'make') as String; - @override - set make(String value) => RealmObject.set(this, 'make', value); - - @override - String? get model => RealmObject.get(this, 'model') as String?; - @override - set model(String? value) => RealmObject.set(this, 'model', value); - - @override - int? get kilometers => RealmObject.get(this, 'kilometers') as int?; - @override - set kilometers(int? value) => RealmObject.set(this, 'kilometers', value); - - @override - Person? get owner => RealmObject.get(this, 'owner') as Person?; - @override - set owner(covariant Person? value) => RealmObject.set(this, 'owner', value); - - @override - Stream> get changes => - RealmObject.getChanges(this); - - @override - Car freeze() => RealmObject.freezeObject(this); - - static SchemaObject get schema => _schema ??= _initSchema(); - static SchemaObject? _schema; - static SchemaObject _initSchema() { - RealmObject.registerFactory(Car._); - return const SchemaObject(Car, 'Car', [ - SchemaProperty('make', RealmPropertyType.string), - SchemaProperty('model', RealmPropertyType.string, optional: true), - SchemaProperty('kilometers', RealmPropertyType.int, optional: true), - SchemaProperty('owner', RealmPropertyType.object, - optional: true, linkTarget: 'Person'), - ]); - } -} - -class Person extends _Person with RealmEntity, RealmObject { - static var _defaultsSet = false; - - Person( - String name, { - int age = 1, - }) { - if (!_defaultsSet) { - _defaultsSet = RealmObject.setDefaults({ - 'age': 1, - }); - } - RealmObject.set(this, 'name', name); - RealmObject.set(this, 'age', age); - } - - Person._(); - - @override - String get name => RealmObject.get(this, 'name') as String; - @override - set name(String value) => RealmObject.set(this, 'name', value); - - @override - int get age => RealmObject.get(this, 'age') as int; - @override - set age(int value) => RealmObject.set(this, 'age', value); - - @override - Stream> get changes => - RealmObject.getChanges(this); - - @override - Person freeze() => RealmObject.freezeObject(this); - - static SchemaObject get schema => _schema ??= _initSchema(); - static SchemaObject? _schema; - static SchemaObject _initSchema() { - RealmObject.registerFactory(Person._); - return const SchemaObject(Person, 'Person', [ - SchemaProperty('name', RealmPropertyType.string), - SchemaProperty('age', RealmPropertyType.int), - ]); - } -} diff --git a/lib/src/cli/metrics/metrics.g.dart b/lib/src/cli/metrics/metrics.g.dart index c56cad387..f8352e52b 100644 --- a/lib/src/cli/metrics/metrics.g.dart +++ b/lib/src/cli/metrics/metrics.g.dart @@ -40,6 +40,7 @@ Properties _$PropertiesFromJson(Map json) => Properties( Map _$PropertiesToJson(Properties instance) { final val = { 'token': instance.token, + 'distinct_id': _digestToJson(instance.distinctId), }; void writeNotNull(String key, dynamic value) { @@ -48,7 +49,6 @@ Map _$PropertiesToJson(Properties instance) { } } - writeNotNull('distinct_id', _digestToJson(instance.distinctId)); writeNotNull('Anonymized MAC Address', const DigestConverter().toJson(instance.anonymizedMacAddress)); writeNotNull('Anonymized Bundle ID', @@ -61,7 +61,7 @@ Map _$PropertiesToJson(Properties instance) { val['Realm Version'] = instance.realmVersion; val['Host OS Type'] = instance.hostOsType; val['Host OS Version'] = instance.hostOsVersion; - writeNotNull('Target OS Type', _$TargetOsTypeEnumMap[instance.targetOsType]); + val['Target OS Type'] = _$TargetOsTypeEnumMap[instance.targetOsType]; writeNotNull('Target OS Version', instance.targetOsVersion); return val; } From 5f4afdea56489c20bbd24b56362d9be9ba3ef2de Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 18 Oct 2022 09:39:40 +0300 Subject: [PATCH 109/113] Set json_annotation version to ^4.7.0 --- flutter/realm_flutter/pubspec.yaml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/flutter/realm_flutter/pubspec.yaml b/flutter/realm_flutter/pubspec.yaml index 4403392c4..e985d423a 100644 --- a/flutter/realm_flutter/pubspec.yaml +++ b/flutter/realm_flutter/pubspec.yaml @@ -20,7 +20,7 @@ dependencies: collection: ^1.16.0 crypto: ^3.0.0 ffi: ^2.0.1 - json_annotation: ^4.4.0 + json_annotation: ^4.7.0 logging: ^1.0.0 meta: ^1.1.8 package_config: ^2.0.0 diff --git a/pubspec.yaml b/pubspec.yaml index 29d1c8cbd..8e1c9c34b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: collection: ^1.16.0 crypto: ^3.0.0 ffi: ^2.0.1 - json_annotation: ^4.6.0 + json_annotation: ^4.7.0 logging: ^1.0.0 meta: ^1.1.8 package_config: ^2.0.0 From fdbd59135892d11355bbf4da384018081e61a725 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 18 Oct 2022 13:34:47 +0300 Subject: [PATCH 110/113] Code review changes --- CHANGELOG.md | 2 +- lib/src/native/realm_core.dart | 13 ++++---- lib/src/realm_class.dart | 19 ++++------- lib/src/session.dart | 2 ++ test/realm_test.dart | 60 ++++++++++++++++++---------------- test/test.dart | 13 ++++---- 6 files changed, 53 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d01ff1e1b..18081f195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Added `Transaction` which is a class that exposes an API for committing and rolling back an active transaction. * Added `realm.beginWriteAsync` which returns a `Future` that resolves when the write lock has been obtained. * Added `realm.writeAsync` which opens an asynchronous transaction, invokes the provided callback, then commits the transaction asynchronously. -* Support `Realm.open` API to asynchronously open a local or remote Realm. When opening a synchronized Realm it will download all the content available at the time the operation began on a background task and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) +* Support `Realm.open` API to asynchronously open a local or synced Realm. When opening a synchronized Realm it will download all the content available at the time the operation began and then return a usable Realm. ([#731](https://github.com/realm/realm-dart/pull/731)) ### Fixed * Added more validations when using `User.apiKeys` to return more meaningful errors when the user cannot perform API key actions - e.g. when the user has been logged in with API key credentials or when the user has been logged out. (Issue [#950](https://github.com/realm/realm-dart/issues/950)) diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index 7682a5586..c0a3f486d 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -1845,13 +1845,14 @@ class _RealmCore { static void _sessionWaitCompletionCallback(Object userdata, Pointer errorCode) { final completer = userdata as Completer; - if (!completer.isCompleted) { - if (errorCode != nullptr) { + if (completer.isCompleted) { + return; + } + if (errorCode != nullptr) { // Throw RealmException instead of RealmError to be recoverable by the user. - completer.completeError(RealmException(errorCode.toSyncError().toString())); - } else { - completer.complete(); - } + completer.completeError(RealmException(errorCode.toSyncError().toString())); + } else { + completer.complete(); } } diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 456048f7a..7dfb77f7e 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -35,7 +35,6 @@ import 'scheduler.dart'; import 'subscription.dart'; import 'session.dart'; -export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; export 'package:realm_common/realm_common.dart' show Ignored, @@ -63,8 +62,6 @@ export 'package:realm_common/realm_common.dart' ObjectId, Uuid; -export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; - // always expose with `show` to explicitly control the public API surface export 'app.dart' show AppConfiguration, MetadataPersistenceMode, App, AppException; export "configuration.dart" @@ -118,7 +115,7 @@ class Realm implements Finalizable { /// Opens a `Realm` using a [Configuration] object. Realm(Configuration config) : this._(config); - Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) : _handle = handle ?? _openRealmSync(config) { + Realm._(this.config, [RealmHandle? handle, this._isInMigration = false]) : _handle = handle ?? _openRealm(config) { _populateMetadata(); isFrozen = realmCore.isFrozen(this); } @@ -134,8 +131,8 @@ class Realm implements Finalizable { /// * `onProgressCallback` - a callback for receiving download progress notifications for synced [Realm]s. /// /// Returns `Future` that completes with the [Realm] once the remote [Realm] is fully synchronized or with a [CancelledException] if operation is canceled. - /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened or if the operation is canceled in advance. - /// Since opening a local Realm is a synchronous operation, there is no benefit of using Realm.open over the constructor. + /// When the configuration is [LocalConfiguration] this completes right after the local [Realm] is opened. + /// Using [open] for opening a local Realm is equivalent to using the constructor of [Realm]. static Future open(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { if (cancellationToken != null && cancellationToken.isCancelled) { throw cancellationToken.exception; @@ -146,11 +143,7 @@ class Realm implements Finalizable { if (config is FlexibleSyncConfiguration) { final session = realm.syncSession; if (onProgressCallback != null) { - subscription = session - .getProgressStream( - ProgressDirection.download, - ProgressMode.forCurrentlyOutstandingWork) - .listen(onProgressCallback); + subscription = session.getProgressStream(ProgressDirection.download, ProgressMode.forCurrentlyOutstandingWork).listen(onProgressCallback); } await session.waitForDownload(cancellationToken); await subscription?.cancel(); @@ -163,7 +156,7 @@ class Realm implements Finalizable { return await CancellableFuture.value(realm, cancellationToken); } - static RealmHandle _openRealmSync(Configuration config) { + static RealmHandle _openRealm(Configuration config) { var dir = File(config.path).parent; if (!dir.existsSync()) { dir.createSync(recursive: true); @@ -773,7 +766,7 @@ class MigrationRealm extends DynamicRealm { } /// The signature of a callback that will be executed while the Realm is opened asynchronously with [Realm.open]. -/// This is the registered callback onProgressCallback to receive progress notifications while the download is in progress. +/// This is the registered onProgressCallback when calling [open] that receives progress notifications while the download is in progress. /// /// * syncProgress - an object of [SyncProgress] that contains `transferredBytes` and `transferableBytes`. /// {@category Realm} diff --git a/lib/src/session.dart b/lib/src/session.dart index bc4170bc2..162544bac 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -19,6 +19,8 @@ import 'dart:async'; import 'dart:ffi'; +import 'package:cancellation_token/cancellation_token.dart'; + import '../realm.dart'; import 'native/realm_core.dart'; import 'user.dart'; diff --git a/test/realm_test.dart b/test/realm_test.dart index d7a28fb2c..89a512fda 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -820,13 +820,15 @@ Future main([List? args]) async { var config = Configuration.local([Friend.schema], path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); var realm = getRealm(config); readFile(String path) { - final bytes = File(path).readAsBytesSync(); + final bytes = File(path).readAsBytesSync(); return utf8.decode(bytes, allowMalformed: true); } + var decoded = readFile(realm.config.path); expect(decoded, contains("bestFriend")); - - config = Configuration.local([Friend.schema], encryptionKey: generateEncryptionKey(), path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); + + config = Configuration.local([Friend.schema], + encryptionKey: generateEncryptionKey(), path: p.join(Configuration.defaultStoragePath, "${generateRandomString(8)}.realm")); realm = getRealm(config); decoded = readFile(realm.config.path); expect(decoded, isNot(contains("bestFriend"))); @@ -1085,7 +1087,7 @@ Future main([List? args]) async { await expectLater(transaction.commitAsync(token), throwsA(isA())); expect(realm.isInTransaction, true); }); - + baasTest('Realm.open (flexibleSync)', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); @@ -1104,7 +1106,7 @@ Future main([List? args]) async { test('Realm.open (local) - cancel before open', () async { final configuration = Configuration.local([Car.schema]); - var cancellationToken = CancellationToken(); + final cancellationToken = CancellationToken(); cancellationToken.cancel(); await expectLater(getRealmAsync(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); @@ -1115,7 +1117,7 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); + final cancellationToken = CancellationToken(); cancellationToken.cancel(); await expectLater(getRealmAsync(configuration, cancellationToken: cancellationToken), throwsA(isA())); }); @@ -1126,8 +1128,8 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); - final isRealmCancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); + final cancellationToken = CancellationToken(); + final isRealmCancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).isCancelled(); cancellationToken.cancel(); expect(await isRealmCancelled, isTrue); }); @@ -1138,43 +1140,43 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); + final cancellationToken = CancellationToken(); - Future.delayed(Duration(milliseconds: 10), () => cancellationToken.cancel()); - final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); - final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).thenIsCancelled(); + Future.delayed(const Duration(milliseconds: 10), () => cancellationToken.cancel()); + final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).isCancelled(); + final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken).isCancelled(); expect(await isRealm1Cancelled, isTrue); expect(await isRealm2Cancelled, isTrue); }); - baasTest('Realm.open (flexibleSync) - open twice the same realm and cancel the first only', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open the same realm twice and only cancel the first call', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken1 = CancellationToken(); - final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken1).thenIsCancelled(); + final cancellationToken1 = CancellationToken(); + final isRealm1Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken1).isCancelled(); - var cancellationToken2 = CancellationToken(); - final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken2).thenIsCancelled(); + final cancellationToken2 = CancellationToken(); + final isRealm2Cancelled = getRealmAsync(configuration, cancellationToken: cancellationToken2).isCancelled(); cancellationToken1.cancel(); expect(await isRealm1Cancelled, isTrue); expect(await isRealm2Cancelled, isFalse); }); - baasTest('Realm.open (flexibleSync) - open two different Realms(for user1 and user2) and cancel only the second', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - open two different Realms for two different users and cancel only the second call', (appConfiguration) async { final app = App(appConfiguration); final user1 = await app.logIn(Credentials.anonymous()); final configuration1 = Configuration.flexibleSync(user1, [Task.schema]); - var cancellationToken1 = CancellationToken(); - final isRealm1Cancelled = getRealmAsync(configuration1, cancellationToken: cancellationToken1).thenIsCancelled(); + final cancellationToken1 = CancellationToken(); + final isRealm1Cancelled = getRealmAsync(configuration1, cancellationToken: cancellationToken1).isCancelled(); final user2 = await app.logIn(Credentials.anonymous(reuseCredentials: false)); final configuration2 = Configuration.flexibleSync(user2, [Task.schema]); - var cancellationToken2 = CancellationToken(); - final isRealm2Cancelled = getRealmAsync(configuration2, cancellationToken: cancellationToken2).thenIsCancelled(); + final cancellationToken2 = CancellationToken(); + final isRealm2Cancelled = getRealmAsync(configuration2, cancellationToken: cancellationToken2).isCancelled(); cancellationToken2.cancel(); expect(await isRealm2Cancelled, isTrue); @@ -1187,7 +1189,7 @@ Future main([List? args]) async { final user = await app.logIn(credentials); final configuration = Configuration.flexibleSync(user, [Task.schema]); - var cancellationToken = CancellationToken(); + final cancellationToken = CancellationToken(); final realm = await getRealmAsync(configuration, cancellationToken: cancellationToken); expect(realm, isNotNull); @@ -1197,7 +1199,7 @@ Future main([List? args]) async { expect(realm.isClosed, false); }); - baasTest('Realm.open (flexibleSync) - listen for download progress of an empty realm', (appConfiguration) async { + baasTest('Realm.open (flexibleSync) - listen for download progress on an empty realm', (appConfiguration) async { final app = App(appConfiguration); final credentials = Credentials.anonymous(); final user = await app.logIn(credentials); @@ -1216,7 +1218,7 @@ Future main([List? args]) async { baasTest('Realm.open (flexibleSync) - download a populated realm', (appConfiguration) async { final app = App(appConfiguration); final queryDifferentiator = generateRandomString(10); - final itemCount = 200; + const itemCount = 200; final config = await _addDataToAtlas(app, queryDifferentiator: queryDifferentiator, itemCount: itemCount); var syncedRealm = await getRealmAsync(config); expect(syncedRealm.isClosed, false); @@ -1245,11 +1247,11 @@ Future main([List? args]) async { final app = App(appConfiguration); final config = await _addDataToAtlas(app); - var cancellationToken = CancellationToken(); + final cancellationToken = CancellationToken(); bool progressReturned = false; final realmIsCancelled = getRealmAsync(config, cancellationToken: cancellationToken, onProgressCallback: (syncProgress) { progressReturned = true; - }).thenIsCancelled(); + }).isCancelled(); cancellationToken.cancel(); expect(await realmIsCancelled, isTrue); expect(progressReturned, isFalse); @@ -1278,8 +1280,8 @@ void openEncryptedRealm(List? encryptionKey, List? decryptionKey, {voi } } -extension _FutureRealm on Future { - Future thenIsCancelled() async { +extension on Future { + Future isCancelled() async { try { final value = await this; expect(value, isNotNull); diff --git a/test/test.dart b/test/test.dart index 265139c21..262ac1fcb 100644 --- a/test/test.dart +++ b/test/test.dart @@ -21,6 +21,7 @@ import 'dart:collection'; import 'dart:ffi'; import 'dart:io'; import 'dart:math'; +import 'package:cancellation_token/cancellation_token.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as _path; import 'package:test/test.dart' hide test; @@ -342,14 +343,12 @@ Realm getRealm(Configuration config) { } Future getRealmAsync(Configuration config, {CancellationToken? cancellationToken, ProgressCallback? onProgressCallback}) async { - { - if (config is FlexibleSyncConfiguration) { - config.sessionStopPolicy = SessionStopPolicy.immediately; - } - final realm = await Realm.open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback); - _openRealms.add(realm); - return realm; + if (config is FlexibleSyncConfiguration) { + config.sessionStopPolicy = SessionStopPolicy.immediately; } + final realm = await Realm.open(config, cancellationToken: cancellationToken, onProgressCallback: onProgressCallback); + _openRealms.add(realm); + return realm; } /// This is needed to make sure the frozen Realm gets forcefully closed by the From 598a71cf0a46f946de15ffd27495e1604a83523e Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 18 Oct 2022 14:32:46 +0300 Subject: [PATCH 111/113] Merge branch 'master' into ds/open_realm_async --- lib/src/realm_class.dart | 2 +- test/realm_test.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/realm_class.dart b/lib/src/realm_class.dart index 7dfb77f7e..76a86bd9f 100644 --- a/lib/src/realm_class.dart +++ b/lib/src/realm_class.dart @@ -20,7 +20,6 @@ import 'dart:async'; import 'dart:ffi'; import 'dart:io'; -import 'package:cancellation_token/cancellation_token.dart'; import 'package:logging/logging.dart'; import 'package:realm_common/realm_common.dart'; import 'package:collection/collection.dart'; @@ -88,6 +87,7 @@ export 'subscription.dart' show Subscription, SubscriptionSet, SubscriptionSetSt export 'user.dart' show User, UserState, UserIdentity, ApiKeyClient, ApiKey; export 'session.dart' show Session, SessionState, ConnectionState, ProgressDirection, ProgressMode, SyncProgress, ConnectionStateChange; export 'migration.dart' show Migration; +export 'package:cancellation_token/cancellation_token.dart' show CancellationToken, CancelledException; /// A [Realm] instance represents a `Realm` database. /// diff --git a/test/realm_test.dart b/test/realm_test.dart index 89a512fda..4e6e993a8 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -20,7 +20,6 @@ import 'dart:convert'; import 'dart:io'; -import 'package:cancellation_token/cancellation_token.dart'; import 'package:test/test.dart' hide test, throws; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/data/latest.dart' as tz; From c58706271a866c1688d860935aa71d701c2a1c2f Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 18 Oct 2022 14:51:27 +0300 Subject: [PATCH 112/113] After merge fixes --- flutter/realm_flutter/example/lib/main.g.dart | 113 ++++++++++++++++++ lib/src/native/realm_core.dart | 1 - lib/src/session.dart | 3 - test/realm_test.dart | 4 +- test/test.dart | 1 - 5 files changed, 115 insertions(+), 7 deletions(-) create mode 100644 flutter/realm_flutter/example/lib/main.g.dart diff --git a/flutter/realm_flutter/example/lib/main.g.dart b/flutter/realm_flutter/example/lib/main.g.dart new file mode 100644 index 000000000..d295cd9da --- /dev/null +++ b/flutter/realm_flutter/example/lib/main.g.dart @@ -0,0 +1,113 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'main.dart'; + +// ************************************************************************** +// RealmObjectGenerator +// ************************************************************************** + +class Car extends _Car with RealmEntity, RealmObject { + static var _defaultsSet = false; + + Car( + String make, { + String? model, + int? kilometers = 500, + Person? owner, + }) { + if (!_defaultsSet) { + _defaultsSet = RealmObject.setDefaults({ + 'kilometers': 500, + }); + } + RealmObject.set(this, 'make', make); + RealmObject.set(this, 'model', model); + RealmObject.set(this, 'kilometers', kilometers); + RealmObject.set(this, 'owner', owner); + } + + Car._(); + + @override + String get make => RealmObject.get(this, 'make') as String; + @override + set make(String value) => RealmObject.set(this, 'make', value); + + @override + String? get model => RealmObject.get(this, 'model') as String?; + @override + set model(String? value) => RealmObject.set(this, 'model', value); + + @override + int? get kilometers => RealmObject.get(this, 'kilometers') as int?; + @override + set kilometers(int? value) => RealmObject.set(this, 'kilometers', value); + + @override + Person? get owner => RealmObject.get(this, 'owner') as Person?; + @override + set owner(covariant Person? value) => RealmObject.set(this, 'owner', value); + + @override + Stream> get changes => RealmObject.getChanges(this); + + @override + Car freeze() => RealmObject.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(Car._); + return const SchemaObject(Car, 'Car', [ + SchemaProperty('make', RealmPropertyType.string), + SchemaProperty('model', RealmPropertyType.string, optional: true), + SchemaProperty('kilometers', RealmPropertyType.int, optional: true), + SchemaProperty('owner', RealmPropertyType.object, optional: true, linkTarget: 'Person'), + ]); + } +} + +class Person extends _Person with RealmEntity, RealmObject { + static var _defaultsSet = false; + + Person( + String name, { + int age = 1, + }) { + if (!_defaultsSet) { + _defaultsSet = RealmObject.setDefaults({ + 'age': 1, + }); + } + RealmObject.set(this, 'name', name); + RealmObject.set(this, 'age', age); + } + + Person._(); + + @override + String get name => RealmObject.get(this, 'name') as String; + @override + set name(String value) => RealmObject.set(this, 'name', value); + + @override + int get age => RealmObject.get(this, 'age') as int; + @override + set age(int value) => RealmObject.set(this, 'age', value); + + @override + Stream> get changes => RealmObject.getChanges(this); + + @override + Person freeze() => RealmObject.freezeObject(this); + + static SchemaObject get schema => _schema ??= _initSchema(); + static SchemaObject? _schema; + static SchemaObject _initSchema() { + RealmObject.registerFactory(Person._); + return const SchemaObject(Person, 'Person', [ + SchemaProperty('name', RealmPropertyType.string), + SchemaProperty('age', RealmPropertyType.int), + ]); + } +} diff --git a/lib/src/native/realm_core.dart b/lib/src/native/realm_core.dart index c0a3f486d..6875c0937 100644 --- a/lib/src/native/realm_core.dart +++ b/lib/src/native/realm_core.dart @@ -25,7 +25,6 @@ import 'dart:typed_data'; import 'package:cancellation_token/cancellation_token.dart'; // Hide StringUtf8Pointer.toNativeUtf8 and StringUtf16Pointer since these allows silently allocating memory. Use toUtf8Ptr instead -import 'package:cancellation_token/cancellation_token.dart'; import 'package:ffi/ffi.dart' hide StringUtf8Pointer, StringUtf16Pointer; import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; diff --git a/lib/src/session.dart b/lib/src/session.dart index 162544bac..cf0e735d8 100644 --- a/lib/src/session.dart +++ b/lib/src/session.dart @@ -18,9 +18,6 @@ import 'dart:async'; import 'dart:ffi'; - -import 'package:cancellation_token/cancellation_token.dart'; - import '../realm.dart'; import 'native/realm_core.dart'; import 'user.dart'; diff --git a/test/realm_test.dart b/test/realm_test.dart index 4e6e993a8..963afe4cc 100644 --- a/test/realm_test.dart +++ b/test/realm_test.dart @@ -23,9 +23,9 @@ import 'dart:io'; import 'package:test/test.dart' hide test, throws; import 'package:timezone/timezone.dart' as tz; import 'package:timezone/data/latest.dart' as tz; -import '../lib/realm.dart'; import 'package:path/path.dart' as p; - +import 'package:cancellation_token/cancellation_token.dart'; +import '../lib/realm.dart'; import 'test.dart'; Future main([List? args]) async { diff --git a/test/test.dart b/test/test.dart index 262ac1fcb..1961eb272 100644 --- a/test/test.dart +++ b/test/test.dart @@ -21,7 +21,6 @@ import 'dart:collection'; import 'dart:ffi'; import 'dart:io'; import 'dart:math'; -import 'package:cancellation_token/cancellation_token.dart'; import 'package:meta/meta.dart'; import 'package:path/path.dart' as _path; import 'package:test/test.dart' hide test; From e97d6e3868c9169e5fc09960108e3cf53dcdf956 Mon Sep 17 00:00:00 2001 From: Desislava Stefanova Date: Tue, 18 Oct 2022 15:33:38 +0300 Subject: [PATCH 113/113] Regenerate main.g.dart --- flutter/realm_flutter/example/lib/main.g.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/flutter/realm_flutter/example/lib/main.g.dart b/flutter/realm_flutter/example/lib/main.g.dart index d295cd9da..96e747646 100644 --- a/flutter/realm_flutter/example/lib/main.g.dart +++ b/flutter/realm_flutter/example/lib/main.g.dart @@ -49,7 +49,8 @@ class Car extends _Car with RealmEntity, RealmObject { set owner(covariant Person? value) => RealmObject.set(this, 'owner', value); @override - Stream> get changes => RealmObject.getChanges(this); + Stream> get changes => + RealmObject.getChanges(this); @override Car freeze() => RealmObject.freezeObject(this); @@ -62,7 +63,8 @@ class Car extends _Car with RealmEntity, RealmObject { SchemaProperty('make', RealmPropertyType.string), SchemaProperty('model', RealmPropertyType.string, optional: true), SchemaProperty('kilometers', RealmPropertyType.int, optional: true), - SchemaProperty('owner', RealmPropertyType.object, optional: true, linkTarget: 'Person'), + SchemaProperty('owner', RealmPropertyType.object, + optional: true, linkTarget: 'Person'), ]); } } @@ -96,7 +98,8 @@ class Person extends _Person with RealmEntity, RealmObject { set age(int value) => RealmObject.set(this, 'age', value); @override - Stream> get changes => RealmObject.getChanges(this); + Stream> get changes => + RealmObject.getChanges(this); @override Person freeze() => RealmObject.freezeObject(this);