@@ -44,6 +44,9 @@ class Store {
4444 final _reader = ReaderWithCBuffer ();
4545 Transaction ? _tx;
4646
47+ /// Path to the database directory.
48+ final String directoryPath;
49+
4750 /// Absolute path to the database directory, used for open check.
4851 final String _absoluteDirectoryPath;
4952
@@ -88,6 +91,7 @@ class Store {
8891 String ? macosApplicationGroup})
8992 : _weak = false ,
9093 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
94+ directoryPath = _safeDirectoryPath (directory),
9195 _absoluteDirectoryPath =
9296 path.context.canonicalize (_safeDirectoryPath (directory)) {
9397 try {
@@ -114,13 +118,11 @@ class Store {
114118
115119 try {
116120 checkObx (C .opt_model (opt, model.ptr));
117- if (directory != null && directory.isNotEmpty) {
118- final cStr = directory.toNativeUtf8 ();
119- try {
120- checkObx (C .opt_directory (opt, cStr.cast ()));
121- } finally {
122- malloc.free (cStr);
123- }
121+ final cStr = directoryPath.toNativeUtf8 ();
122+ try {
123+ checkObx (C .opt_directory (opt, cStr.cast ()));
124+ } finally {
125+ malloc.free (cStr);
124126 }
125127 if (maxDBSizeInKB != null && maxDBSizeInKB > 0 ) {
126128 C .opt_max_db_size_in_kb (opt, maxDBSizeInKB);
@@ -199,6 +201,7 @@ class Store {
199201 {bool queriesCaseSensitiveDefault = true })
200202 // must not close the same native store twice so [_weak]=true
201203 : _weak = true ,
204+ directoryPath = '' ,
202205 _absoluteDirectoryPath = '' ,
203206 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault {
204207 // see [reference] for serialization order
@@ -231,6 +234,7 @@ class Store {
231234 // _weak = false so store can be closed.
232235 : _weak = false ,
233236 _queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
237+ directoryPath = _safeDirectoryPath (directoryPath),
234238 _absoluteDirectoryPath =
235239 path.context.canonicalize (_safeDirectoryPath (directoryPath)) {
236240 try {
@@ -240,12 +244,12 @@ class Store {
240244 // overlap.
241245 _checkStoreDirectoryNotOpen ();
242246
243- final path = _safeDirectoryPath (directoryPath);
244- final pathCStr = path.toNativeUtf8 ();
247+ final pathCStr = this .directoryPath.toNativeUtf8 ();
245248 try {
246249 if (debugLogs) {
247250 final isOpen = C .store_is_open (pathCStr.cast ());
248- print ('Attaching to store... path=$path isOpen=$isOpen ' );
251+ print (
252+ 'Attaching to store... path=${this .directoryPath } isOpen=$isOpen ' );
249253 }
250254 _cStore = C .store_attach (pathCStr.cast ());
251255 } finally {
@@ -378,6 +382,45 @@ class Store {
378382 return _runInTransaction (mode, (tx) => fn ());
379383 }
380384
385+ // Isolate entry point must be static or top-level.
386+ static Future <void > _callFunctionWithStoreInIsolate <P , R >(
387+ _IsoPass <P , R > isoPass) async {
388+ final store = Store .attach (isoPass.model, isoPass.dbDirectoryPath,
389+ queriesCaseSensitiveDefault: isoPass.queriesCaseSensitiveDefault);
390+ final result = await isoPass.runFn (store);
391+ store.close ();
392+ // Note: maybe replace with Isolate.exit (and remove kill call in
393+ // runIsolated) once min Dart SDK 2.15.
394+ isoPass.resultPort? .send (result);
395+ }
396+
397+ /// Spawns an isolate, runs [callback] in that isolate passing it [param] with
398+ /// its own Store and returns the result of callback.
399+ ///
400+ /// Instances of [callback] must be top-level functions or static methods
401+ /// of classes, not closures or instance methods of objects.
402+ ///
403+ /// Note: this requires Dart 2.15.0 or newer
404+ /// (shipped with Flutter 2.8.0 or newer).
405+ Future <R > runIsolated <P , R >(
406+ TxMode mode, FutureOr <R > Function (Store , P ) callback, P param) async {
407+ final resultPort = ReceivePort ();
408+ // Await isolate spawn to avoid waiting forever if it fails to spawn.
409+ final isolate = await Isolate .spawn (
410+ _callFunctionWithStoreInIsolate,
411+ _IsoPass (_defs, directoryPath, _queriesCaseSensitiveDefault,
412+ resultPort.sendPort, callback, param));
413+ // Use Completer to return result so type is not lost.
414+ final result = Completer <R >();
415+ resultPort.listen ((dynamic message) {
416+ result.complete (message as R );
417+ });
418+ await result.future;
419+ resultPort.close ();
420+ isolate.kill ();
421+ return result.future;
422+ }
423+
381424 /// Internal only - bypasses the main checks for async functions, you may
382425 /// only pass synchronous callbacks!
383426 R _runInTransaction <R >(TxMode mode, R Function (Transaction ) fn) {
@@ -491,3 +534,38 @@ final _openStoreDirectories = HashSet<String>();
491534/// Otherwise, it's we can distinguish at runtime whether a function is async.
492535final _nullSafetyEnabled = _nullReturningFn is ! Future Function ();
493536final _nullReturningFn = () => null ;
537+
538+ /// Captures everything required to create a "copy" of a store in an isolate
539+ /// and run user code.
540+ @immutable
541+ class _IsoPass <P , R > {
542+ final ModelDefinition model;
543+
544+ /// Used to attach to store in separate isolate
545+ /// (may be replaced in the future).
546+ final String dbDirectoryPath;
547+
548+ final bool queriesCaseSensitiveDefault;
549+
550+ /// Non-void functions can use this port to receive the result.
551+ final SendPort ? resultPort;
552+
553+ /// Parameter passed to [callback] .
554+ final P param;
555+
556+ /// To be called in isolate.
557+ final FutureOr <R > Function (Store , P ) callback;
558+
559+ const _IsoPass (
560+ this .model,
561+ this .dbDirectoryPath,
562+ // ignore: avoid_positional_boolean_parameters
563+ this .queriesCaseSensitiveDefault,
564+ this .resultPort,
565+ this .callback,
566+ this .param);
567+
568+ /// Calls [callback] inside this class so types are not lost
569+ /// (if called in isolate types would be dynamic instead of P and R).
570+ FutureOr <R > runFn (Store store) => callback (store, param);
571+ }
0 commit comments