diff --git a/mobile/lib/infrastructure/repositories/db.repository.dart b/mobile/lib/infrastructure/repositories/db.repository.dart index fd5d03a8e5..2499a08a36 100644 --- a/mobile/lib/infrastructure/repositories/db.repository.dart +++ b/mobile/lib/infrastructure/repositories/db.repository.dart @@ -1,7 +1,8 @@ import 'dart:async'; +import 'dart:io'; import 'package:drift/drift.dart'; -import 'package:drift_flutter/drift_flutter.dart'; +import 'package:drift_sqlite_async/drift_sqlite_async.dart'; import 'package:flutter/foundation.dart'; import 'package:immich_mobile/infrastructure/entities/asset_edit.entity.dart'; import 'package:immich_mobile/infrastructure/entities/asset_face.entity.dart'; @@ -13,7 +14,6 @@ import 'package:immich_mobile/infrastructure/entities/local_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/memory.entity.dart'; import 'package:immich_mobile/infrastructure/entities/memory_asset.entity.dart'; -import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'; import 'package:immich_mobile/infrastructure/entities/partner.entity.dart'; import 'package:immich_mobile/infrastructure/entities/person.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_album.entity.dart'; @@ -22,6 +22,7 @@ import 'package:immich_mobile/infrastructure/entities/remote_album_user.entity.d import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/remote_asset_cloud_id.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/settings.entity.dart'; import 'package:immich_mobile/infrastructure/entities/stack.entity.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/trashed_local_asset.entity.dart'; @@ -31,6 +32,11 @@ import 'package:immich_mobile/infrastructure/entities/user_metadata.entity.dart' import 'package:immich_mobile/infrastructure/repositories/db.repository.drift.dart'; import 'package:immich_mobile/infrastructure/repositories/db.repository.steps.dart'; import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; +import 'package:sqlite3/sqlite3.dart'; +import 'package:sqlite_async/native.dart'; +import 'package:sqlite_async/sqlite_async.dart'; @DriftDatabase( tables: [ @@ -60,8 +66,9 @@ import 'package:logging/logging.dart'; include: {'package:immich_mobile/infrastructure/entities/merged_asset.drift'}, ) class Drift extends $Drift { - Drift([QueryExecutor? executor]) - : super(executor ?? driftDatabase(name: 'immich', native: const DriftNativeOptions(shareAcrossIsolates: true))); + Drift(super.executor); + + Drift.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db)); Future reset() async { // https://github.com/simolus3/drift/commit/bd80a46264b6dd833ef4fd87fffc03f5a832ab41#diff-3f879e03b4a35779344ef16170b9353608dd9c42385f5402ec6035aac4dd8a04R76-R94 @@ -311,3 +318,41 @@ class DriftDatabaseRepository { Future transaction(Future Function() callback) => _db.transaction(callback); } + +Future openSqliteConnection({required String name}) async { + final dbFolder = await getApplicationDocumentsDirectory(); + final file = File(p.join(dbFolder.path, '$name.sqlite')); + return SqliteDatabase.withFactory( + _ImmichSqliteOpenFactory( + path: file.path, + sqliteOptions: const SqliteOptions( + journalMode: SqliteJournalMode.wal, // PRAGMA journal_mode (writer only) + synchronous: SqliteSynchronous.normal, // PRAGMA synchronous + lockTimeout: Duration(seconds: 30), // -> PRAGMA busy_timeout = 30000 + ), + ), + ); +} + +final class _ImmichSqliteOpenFactory extends NativeSqliteOpenFactory { + _ImmichSqliteOpenFactory({required super.path, super.sqliteOptions}); + + @override + List pragmaStatements(SqliteOpenOptions options) { + return [ + ...super.pragmaStatements(options), + 'PRAGMA cache_size = -32000', // 32MB + 'PRAGMA temp_store = MEMORY', + 'PRAGMA foreign_keys = ON', + ]; + } +} + +Future configureSqliteCache() async { + // Make sqlite3 pick a more suitable location for temporary files - the + // one from the system may be inaccessible due to sand-boxing. + final cacheBase = (await getTemporaryDirectory()).path; + // We can't access /tmp on Android, which sqlite3 would try by default. + // Explicitly tell it about the correct temporary directory. + sqlite3.tempDirectory = cacheBase; +} diff --git a/mobile/lib/infrastructure/repositories/logger_db.repository.dart b/mobile/lib/infrastructure/repositories/logger_db.repository.dart index d11174356d..32af4af748 100644 --- a/mobile/lib/infrastructure/repositories/logger_db.repository.dart +++ b/mobile/lib/infrastructure/repositories/logger_db.repository.dart @@ -1,14 +1,14 @@ import 'package:drift/drift.dart'; -import 'package:drift_flutter/drift_flutter.dart'; +import 'package:drift_sqlite_async/drift_sqlite_async.dart'; import 'package:immich_mobile/infrastructure/entities/log.entity.dart'; import 'package:immich_mobile/infrastructure/repositories/logger_db.repository.drift.dart'; +import 'package:sqlite_async/sqlite_async.dart'; @DriftDatabase(tables: [LogMessageEntity]) class DriftLogger extends $DriftLogger { - DriftLogger([QueryExecutor? executor]) - : super( - executor ?? driftDatabase(name: 'immich_logs', native: const DriftNativeOptions(shareAcrossIsolates: true)), - ); + DriftLogger.fromExecutor(super.executor); + + DriftLogger.sqlite(SqliteConnection db) : super(SqliteAsyncDriftConnection(db)); @override int get schemaVersion => 1; @@ -19,7 +19,8 @@ class DriftLogger extends $DriftLogger { await customStatement('PRAGMA foreign_keys = ON'); await customStatement('PRAGMA synchronous = NORMAL'); await customStatement('PRAGMA journal_mode = WAL'); - await customStatement('PRAGMA busy_timeout = 500'); + await customStatement('PRAGMA busy_timeout = 30000'); // 30s + await customStatement('PRAGMA cache_size = -32000'); // 32MB await customStatement('PRAGMA temp_store = MEMORY'); }, ); diff --git a/mobile/lib/utils/bootstrap.dart b/mobile/lib/utils/bootstrap.dart index 9bd652381a..1c27d7ea93 100644 --- a/mobile/lib/utils/bootstrap.dart +++ b/mobile/lib/utils/bootstrap.dart @@ -43,8 +43,9 @@ void configureFileDownloaderNotifications() { abstract final class Bootstrap { static Future<(Drift, DriftLogger)> initDomain({bool listenStoreUpdates = true, bool shouldBufferLogs = true}) async { - final drift = Drift(); - final logDb = DriftLogger(); + await configureSqliteCache(); + final drift = Drift.sqlite(await openSqliteConnection(name: 'immich')); + final logDb = DriftLogger.sqlite(await openSqliteConnection(name: 'immich_logs')); final DriftStoreRepository storeRepo = DriftStoreRepository(drift); await StoreService.init(storeRepository: storeRepo, listenUpdates: listenStoreUpdates); diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 72bdc6b298..9323cedf11 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -370,14 +370,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.33.0" - drift_flutter: + drift_sqlite_async: dependency: "direct main" description: - name: drift_flutter - sha256: "887fdec622174dc7eaefd0048403e34ee07cc18626ac8a7544cc3b8a4a172166" + name: drift_sqlite_async + sha256: "28c666847ecbc2e90dbcf8e8c2eecbf847a38af7e6480efc08f3a3c39e060f8d" url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1" dynamic_color: dependency: "direct main" description: @@ -1627,30 +1627,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.2" - sqlcipher_flutter_libs: - dependency: transitive - description: - name: sqlcipher_flutter_libs - sha256: "38d62d659d2fb8739bf25a42c9a350d1fdd6c29a5a61f13a946778ec75d27929" - url: "https://pub.dev" - source: hosted - version: "0.7.0+eol" sqlite3: - dependency: transitive + dependency: "direct main" description: name: sqlite3 - sha256: "56da3e13ed7d28a66f930aa2b2b29db6736a233f08283326e96321dd812030f5" + sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189" url: "https://pub.dev" source: hosted - version: "3.3.1" - sqlite3_flutter_libs: + version: "3.3.2" + sqlite3_connection_pool: dependency: transitive description: - name: sqlite3_flutter_libs - sha256: "3ed7553eee7bb368f8950f58ba29f634e06e813c029aff6a0d60862b96de8454" + name: sqlite3_connection_pool + sha256: "9d2b3b398b03c96743fd071521fc665be73c33c9cd5c56d87196baff8d8b4398" url: "https://pub.dev" source: hosted - version: "0.6.0+eol" + version: "0.2.6" + sqlite3_web: + dependency: transitive + description: + name: sqlite3_web + sha256: fab6f6921f8fc3f703536c018d709ccb4ad5654d92f53e9b206c222f77c50021 + url: "https://pub.dev" + source: hosted + version: "0.8.1" + sqlite_async: + dependency: "direct main" + description: + name: sqlite_async + sha256: e3e486e536253e17260c52b6f824b749adff3f1fed878d4abe629796b858290b + url: "https://pub.dev" + source: hosted + version: "0.14.2" sqlparser: dependency: transitive description: diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 206f9ac2b3..67b9f2a17e 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: crypto: ^3.0.7 device_info_plus: ^12.4.0 drift: ^2.32.1 - drift_flutter: ^0.3.0 + drift_sqlite_async: 0.3.1 dynamic_color: ^1.8.1 easy_localization: ^3.0.8 ffi: ^2.2.0 @@ -66,6 +66,8 @@ dependencies: share_plus: ^10.1.4 sliver_tools: ^0.2.12 stream_transform: ^2.1.1 + sqlite3: ^3.3.2 + sqlite_async: 0.14.2 thumbhash: 0.1.0+1 timezone: ^0.9.4 url_launcher: ^6.3.2