129 lines
4.4 KiB
Dart
129 lines
4.4 KiB
Dart
// 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<DatabaseConnection>.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<EncKeysService>();
|
|
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);
|
|
}
|