// Dart imports: import 'dart:io'; import 'dart:isolate'; // Flutter imports: import 'package:flutter/foundation.dart' show kDebugMode; // Package imports: import 'package:drift/drift.dart'; import 'package:drift/isolate.dart'; import 'package:drift/native.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:remever/inject.dart'; import 'package:remever/services/core/enc_keys_service.dart'; /// Obtains a database connection for running drift in a Dart VM. /// /// The [NativeDatabase] from drift will synchronously use sqlite3's C APIs. /// To move synchronous database work off the main thread, we use a /// [DriftIsolate], which can run queries in a background isolate under the /// hood. /// DatabaseConnection connect() { return DatabaseConnection.delayed( Future.sync(() async { // Background isolates can't use platform channels, so let's use // `path_provider` in the main isolate and just send the result containing // the path over to the background isolate. final Directory appDir = await getApplicationDocumentsDirectory(); final Directory dbDir = Directory(p.join(appDir.path, 'databases')); if (!dbDir.existsSync()) { await dbDir.create(recursive: true); } final String dbPath = p.join(appDir.path, 'databases', 'app.db.enc'); final EncKeysService encKeyService = getIt(); final String key = await encKeyService.getRawKey('db'); final ReceivePort receiveDriftIsolate = ReceivePort(); await Isolate.spawn( _entrypointForDriftIsolate, _IsolateStartRequest(receiveDriftIsolate.sendPort, dbPath, key), ); final DriftIsolate driftIsolate = await receiveDriftIsolate.first as DriftIsolate; return driftIsolate.connect(); }), ); } /// The entrypoint of isolates can only take a single message, but we need two /// (a send port to reach the originating isolate and the database's path that /// should be opened on the background isolate). So, we bundle this information /// in a single class. class _IsolateStartRequest { final SendPort talkToMain; final String databasePath; final String key; _IsolateStartRequest(this.talkToMain, this.databasePath, this.key); } /// The entrypoint for a background isolate launching a drift server. /// /// The main isolate can then connect to that isolate server to transparently /// run queries in the background. void _entrypointForDriftIsolate(_IsolateStartRequest request) { /// @see https://drift.simonbinder.eu/docs/other-engines/encryption/ // open // ..overrideFor(OperatingSystem.android, openCipherOnAndroid) // ..overrideFor( // OperatingSystem.linux, // () => DynamicLibrary.open('libsqlcipher.so'), // ) // ..overrideFor( // OperatingSystem.windows, // () => DynamicLibrary.open('sqlcipher.dll'), // ); if (kDebugMode) { print('[DRIFT] Path -> ${request.databasePath}'); } // The native database synchronously uses sqlite3's C API with `dart:ffi` for // a fast database implementation that doesn't require platform channels. final NativeDatabase databaseImpl = NativeDatabase( File(request.databasePath), logStatements: false, // setup: (Database db) { // // Check that we're actually running with SQLCipher by quering the // // cipher_version pragma. // if (Platform.isAndroid) { // final ResultSet result = db.select('pragma cipher_version'); // if (result.isEmpty) { // throw UnsupportedError( // 'This database needs to run with SQLCipher, but that library is ' // 'not available!', // ); // } // } // // Then, apply the key to encrypt the database. Unfortunately, this // // pragma doesn't seem to support prepared statements so we inline the // // key. // final String escapedKey = request.key.replaceAll("'", "''"); // db.execute("pragma key = '$escapedKey'"); // // Test that the key is correct by selecting from a table // db.execute('select count(*) from sqlite_master'); // }, ); // We can use DriftIsolate.inCurrent because this function is the entrypoint // of a background isolate itself. final DriftIsolate driftServer = DriftIsolate.inCurrent( () => DatabaseConnection(databaseImpl), ); // Inform the main isolate about the server we just created. request.talkToMain.send(driftServer); }