import 'dart:isolate'; import 'package:drift/drift.dart'; import 'package:drift/isolate.dart'; import 'package:flutter/foundation.dart'; import 'package:kiwi/kiwi.dart'; import 'package:nc_photos/app_init.dart' as app_init; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/sqlite_table.dart'; import 'package:nc_photos/mobile/platform.dart' if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform; import 'package:nc_photos/platform/k.dart' as platform_k; typedef ComputeWithDbCallback = Future Function( SqliteDb db, T message); /// Create a drift db running in an isolate /// /// This is only expected to be used in the main isolate Future createDb() async { // see: https://drift.simonbinder.eu/docs/advanced-features/isolates/ final driftIsolate = await _createDriftIsolate(); final connection = await driftIsolate.connect(); return SqliteDb.connect(connection); } Future computeWithDb( ComputeWithDbCallback callback, T args) async { if (platform_k.isWeb) { final c = KiwiContainer().resolve(); return await callback(c.sqliteDb, args); } else { return await compute( _computeWithDbImpl, _ComputeWithDbMessage( await platform.getSqliteConnectionArgs(), callback, args), ); } } class _IsolateStartRequest { const _IsolateStartRequest(this.sendDriftIsolate, this.platformArgs); final SendPort sendDriftIsolate; final Map platformArgs; } class _ComputeWithDbMessage { const _ComputeWithDbMessage( this.sqliteConnectionArgs, this.callback, this.args); final Map sqliteConnectionArgs; final ComputeWithDbCallback callback; final T args; } Future _createDriftIsolate() async { final args = await platform.getSqliteConnectionArgs(); final receivePort = ReceivePort(); await Isolate.spawn( _startBackground, _IsolateStartRequest(receivePort.sendPort, args), ); // _startBackground will send the DriftIsolate to this ReceivePort return await receivePort.first as DriftIsolate; } void _startBackground(_IsolateStartRequest request) { app_init.initDrift(); // this is the entry point from the background isolate! Let's create // the database from the path we received final executor = platform.openSqliteConnectionWithArgs(request.platformArgs); // we're using DriftIsolate.inCurrent here as this method already runs on a // background isolate. If we used DriftIsolate.spawn, a third isolate would be // started which is not what we want! final driftIsolate = DriftIsolate.inCurrent( () => DatabaseConnection.fromExecutor(executor), // this breaks background service! serialize: false, ); // inform the starting isolate about this, so that it can call .connect() request.sendDriftIsolate.send(driftIsolate); } Future _computeWithDbImpl(_ComputeWithDbMessage message) async { app_init.initDrift(); // we don't use driftIsolate because opening a DB normally is found to perform // better final sqliteDb = SqliteDb( executor: platform.openSqliteConnectionWithArgs(message.sqliteConnectionArgs), ); try { return await message.callback(sqliteDb, message.args); } finally { await sqliteDb.close(); } }