@@ -44,6 +44,9 @@ class Store {
44
44
final _reader = ReaderWithCBuffer ();
45
45
Transaction ? _tx;
46
46
47
+ /// Path to the database directory.
48
+ final String directoryPath;
49
+
47
50
/// Absolute path to the database directory, used for open check.
48
51
final String _absoluteDirectoryPath;
49
52
@@ -88,6 +91,7 @@ class Store {
88
91
String ? macosApplicationGroup})
89
92
: _weak = false ,
90
93
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
94
+ directoryPath = _safeDirectoryPath (directory),
91
95
_absoluteDirectoryPath =
92
96
path.context.canonicalize (_safeDirectoryPath (directory)) {
93
97
try {
@@ -114,13 +118,11 @@ class Store {
114
118
115
119
try {
116
120
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);
124
126
}
125
127
if (maxDBSizeInKB != null && maxDBSizeInKB > 0 ) {
126
128
C .opt_max_db_size_in_kb (opt, maxDBSizeInKB);
@@ -199,6 +201,7 @@ class Store {
199
201
{bool queriesCaseSensitiveDefault = true })
200
202
// must not close the same native store twice so [_weak]=true
201
203
: _weak = true ,
204
+ directoryPath = '' ,
202
205
_absoluteDirectoryPath = '' ,
203
206
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault {
204
207
// see [reference] for serialization order
@@ -231,6 +234,7 @@ class Store {
231
234
// _weak = false so store can be closed.
232
235
: _weak = false ,
233
236
_queriesCaseSensitiveDefault = queriesCaseSensitiveDefault,
237
+ directoryPath = _safeDirectoryPath (directoryPath),
234
238
_absoluteDirectoryPath =
235
239
path.context.canonicalize (_safeDirectoryPath (directoryPath)) {
236
240
try {
@@ -240,12 +244,12 @@ class Store {
240
244
// overlap.
241
245
_checkStoreDirectoryNotOpen ();
242
246
243
- final path = _safeDirectoryPath (directoryPath);
244
- final pathCStr = path.toNativeUtf8 ();
247
+ final pathCStr = this .directoryPath.toNativeUtf8 ();
245
248
try {
246
249
if (debugLogs) {
247
250
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 ' );
249
253
}
250
254
_cStore = C .store_attach (pathCStr.cast ());
251
255
} finally {
@@ -378,6 +382,45 @@ class Store {
378
382
return _runInTransaction (mode, (tx) => fn ());
379
383
}
380
384
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
+
381
424
/// Internal only - bypasses the main checks for async functions, you may
382
425
/// only pass synchronous callbacks!
383
426
R _runInTransaction <R >(TxMode mode, R Function (Transaction ) fn) {
@@ -491,3 +534,38 @@ final _openStoreDirectories = HashSet<String>();
491
534
/// Otherwise, it's we can distinguish at runtime whether a function is async.
492
535
final _nullSafetyEnabled = _nullReturningFn is ! Future Function ();
493
536
final _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