diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml
index a7ba0a5d..ecf597ea 100644
--- a/app/android/app/src/main/AndroidManifest.xml
+++ b/app/android/app/src/main/AndroidManifest.xml
@@ -31,15 +31,6 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
-
-
diff --git a/app/build.yaml b/app/build.yaml
new file mode 100644
index 00000000..5cffa1e9
--- /dev/null
+++ b/app/build.yaml
@@ -0,0 +1,10 @@
+targets:
+ $default:
+ builders:
+ drift_dev:
+ options:
+ apply_converters_on_variables: true
+ generate_values_in_copy_with: true
+ new_sql_code_generation: true
+ scoped_dart_components: true
+ generate_connect_constructor: true
diff --git a/app/lib/app_db.dart b/app/lib/app_db.dart
deleted file mode 100644
index a2c7a97c..00000000
--- a/app/lib/app_db.dart
+++ /dev/null
@@ -1,499 +0,0 @@
-import 'dart:async';
-
-import 'package:equatable/equatable.dart';
-import 'package:idb_shim/idb.dart';
-import 'package:logging/logging.dart';
-import 'package:nc_photos/account.dart';
-import 'package:nc_photos/ci_string.dart';
-import 'package:nc_photos/entity/album.dart';
-import 'package:nc_photos/entity/album/upgrader.dart';
-import 'package:nc_photos/entity/file.dart';
-import 'package:nc_photos/k.dart' as k;
-import 'package:nc_photos/mobile/platform.dart'
- if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
-import 'package:nc_photos/num_extension.dart';
-import 'package:nc_photos/object_extension.dart';
-import 'package:nc_photos/platform/k.dart' as platform_k;
-import 'package:nc_photos/type.dart';
-
-class AppDb {
- static const dbName = "app.db";
- static const dbVersion = 6;
- static const albumStoreName = "albums";
- static const file2StoreName = "files2";
- static const dirStoreName = "dirs";
- static const metaStoreName = "meta";
-
- /// Run [fn] with an opened database instance
- ///
- /// This function guarantees that:
- /// 1) Database is always closed after [fn] exits, even with an error
- /// 2) Only at most 1 database instance being opened at any time
- Future use(Transaction Function(Database db) transactionBuilder,
- FutureOr Function(Transaction transaction) fn) async {
- // make sure only one client is opening the db
- return await platform.Lock.synchronized(k.appDbLockId, () async {
- final db = await _open();
- Transaction? transaction;
- try {
- transaction = transactionBuilder(db);
- return await fn(transaction);
- } finally {
- if (transaction != null) {
- await transaction.completed;
- }
- db.close();
- }
- });
- }
-
- Future delete() async {
- _log.warning("[delete] Deleting database");
- return await platform.Lock.synchronized(k.appDbLockId, () async {
- final dbFactory = platform.getDbFactory();
- await dbFactory.deleteDatabase(dbName);
- });
- }
-
- /// Open the database
- Future _open() {
- if (platform_k.isWeb) {
- return _openNative();
- } else {
- return _openSqflite();
- }
- }
-
- /// Open the sqflite database
- ///
- /// We can't simply call deleteObjectStore on upgrade failure here, as the
- /// package does not remove the corresponding indexes, so when we recreate
- /// the indexes later, it'll fail. What we do here, is to delete the whole
- /// database instead
- Future _openSqflite() async {
- final dbFactory = platform.getDbFactory();
- try {
- int? fromVersion, toVersion;
- final db = await dbFactory.open(
- dbName,
- version: dbVersion,
- onUpgradeNeeded: (event) {
- _upgrade(event);
- fromVersion = event.oldVersion;
- toVersion = event.newVersion;
- },
- );
- if (fromVersion != null && toVersion != null) {
- await _onPostUpgrade(db, fromVersion!, toVersion!);
- }
- return db;
- } catch (e, stackTrace) {
- _log.shout(
- "[_openSqflite] Failed while upgrading database", e, stackTrace);
- _log.warning("[_openSqflite] Recreating db");
- await dbFactory.deleteDatabase(dbName);
- return dbFactory.open(dbName,
- version: dbVersion, onUpgradeNeeded: _upgrade);
- }
- }
-
- /// Open the native IndexedDB database
- ///
- /// Errors thrown in onUpgradeNeeded are not propagated properly to us on web,
- /// so the sqflite approach will not work
- Future _openNative() async {
- final dbFactory = platform.getDbFactory();
- int? fromVersion, toVersion;
- final db = await dbFactory.open(
- dbName,
- version: dbVersion,
- onUpgradeNeeded: (event) {
- try {
- _upgrade(event);
- fromVersion = event.oldVersion;
- toVersion = event.newVersion;
- } catch (e, stackTrace) {
- _log.shout(
- "[_openNative] Failed while upgrading database", e, stackTrace);
- // drop the db and rebuild a new one instead
- try {
- event.database.deleteObjectStore(albumStoreName);
- } catch (_) {}
- try {
- event.database.deleteObjectStore(file2StoreName);
- } catch (_) {}
- try {
- event.database.deleteObjectStore(dirStoreName);
- } catch (_) {}
- try {
- event.database.deleteObjectStore(metaStoreName);
- } catch (_) {}
- try {
- event.database.deleteObjectStore(_fileDbStoreName);
- } catch (_) {}
- try {
- event.database.deleteObjectStore(_fileStoreName);
- } catch (_) {}
- _log.warning("[_openNative] Recreating db");
- _upgrade(_DummyVersionChangeEvent(
- 0,
- event.newVersion,
- event.transaction,
- event.target,
- event.currentTarget,
- event.database));
- }
- },
- );
- if (fromVersion != null && toVersion != null) {
- await _onPostUpgrade(db, fromVersion!, toVersion!);
- }
- return db;
- }
-
- void _upgrade(VersionChangeEvent event) {
- _log.info("[_upgrade] Upgrade database: ${event.oldVersion} -> $dbVersion");
-
- final db = event.database;
- // ignore: unused_local_variable
- ObjectStore? albumStore, file2Store, dirStore, metaStore;
- if (event.oldVersion < 2) {
- // version 2 store things in a new way, just drop all
- try {
- db.deleteObjectStore(albumStoreName);
- } catch (_) {}
- albumStore = db.createObjectStore(albumStoreName);
- albumStore.createIndex(
- AppDbAlbumEntry.indexName, AppDbAlbumEntry.keyPath);
- }
- if (event.oldVersion < 3) {
- // new object store in v3
- // no longer relevant in v4
-
- // recreate file store from scratch
- // no longer relevant in v4
- }
- if (event.oldVersion < 4) {
- try {
- db.deleteObjectStore(_fileDbStoreName);
- } catch (_) {}
- try {
- db.deleteObjectStore(_fileStoreName);
- } catch (_) {}
-
- file2Store = db.createObjectStore(file2StoreName);
- file2Store.createIndex(AppDbFile2Entry.strippedPathIndexName,
- AppDbFile2Entry.strippedPathKeyPath);
-
- dirStore = db.createObjectStore(dirStoreName);
- }
- file2Store ??= event.transaction.objectStore(file2StoreName);
- if (event.oldVersion < 5) {
- file2Store.createIndex(AppDbFile2Entry.dateTimeEpochMsIndexName,
- AppDbFile2Entry.dateTimeEpochMsKeyPath);
-
- metaStore =
- db.createObjectStore(metaStoreName, keyPath: AppDbMetaEntry.keyPath);
- }
- if (event.oldVersion < 6) {
- file2Store.createIndex(AppDbFile2Entry.fileIsFavoriteIndexName,
- AppDbFile2Entry.fileIsFavoriteKeyPath);
- }
- }
-
- Future _onPostUpgrade(
- Database db, int fromVersion, int toVersion) async {
- if (fromVersion.inRange(1, 4) && toVersion >= 5) {
- final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
- final metaStore = transaction.objectStore(AppDb.metaStoreName);
- await metaStore
- .put(const AppDbMetaEntryDbCompatV5(false).toEntry().toJson());
- await transaction.completed;
- }
- }
-
- static const _fileDbStoreName = "filesDb";
- static const _fileStoreName = "files";
-
- static final _log = Logger("app_db.AppDb");
-}
-
-class AppDbAlbumEntry {
- static const indexName = "albumStore_path_index";
- static const keyPath = ["path", "index"];
- static const maxDataSize = 160;
-
- AppDbAlbumEntry(this.path, this.index, this.album);
-
- JsonObj toJson() {
- return {
- "path": path,
- "index": index,
- "album": album.toAppDbJson(),
- };
- }
-
- factory AppDbAlbumEntry.fromJson(JsonObj json, Account account) {
- return AppDbAlbumEntry(
- json["path"],
- json["index"],
- Album.fromJson(
- json["album"].cast(),
- upgraderFactory: DefaultAlbumUpgraderFactory(
- account: account,
- logFilePath: json["path"],
- ),
- )!,
- );
- }
-
- static String toPath(Account account, String filePath) =>
- "${account.url}/$filePath";
- static String toPathFromFile(Account account, File albumFile) =>
- toPath(account, albumFile.path);
- static String toPrimaryKey(Account account, File albumFile, int index) =>
- "${toPathFromFile(account, albumFile)}[$index]";
-
- final String path;
- final int index;
- // properties other than Album.items is undefined when index > 0
- final Album album;
-}
-
-class AppDbFile2Entry with EquatableMixin {
- static const strippedPathIndexName = "server_userId_strippedPath";
- static const strippedPathKeyPath = ["server", "userId", "strippedPath"];
-
- static const dateTimeEpochMsIndexName = "server_userId_dateTimeEpochMs";
- static const dateTimeEpochMsKeyPath = ["server", "userId", "dateTimeEpochMs"];
-
- static const fileIsFavoriteIndexName = "server_userId_fileIsFavorite";
- static const fileIsFavoriteKeyPath = ["server", "userId", "file.isFavorite"];
-
- AppDbFile2Entry(this.server, this.userId, this.strippedPath,
- this.dateTimeEpochMs, this.file);
-
- factory AppDbFile2Entry.fromFile(Account account, File file) =>
- AppDbFile2Entry(account.url, account.username, file.strippedPathWithEmpty,
- file.bestDateTime.millisecondsSinceEpoch, file);
-
- factory AppDbFile2Entry.fromJson(JsonObj json) => AppDbFile2Entry(
- json["server"],
- (json["userId"] as String).toCi(),
- json["strippedPath"],
- json["dateTimeEpochMs"],
- File.fromJson(json["file"].cast()),
- );
-
- JsonObj toJson() => {
- "server": server,
- "userId": userId.toCaseInsensitiveString(),
- "strippedPath": strippedPath,
- "dateTimeEpochMs": dateTimeEpochMs,
- "file": file.toJson(),
- };
-
- static String toPrimaryKey(Account account, int fileId) =>
- "${account.url}/${account.username.toCaseInsensitiveString()}/$fileId";
-
- static String toPrimaryKeyForFile(Account account, File file) =>
- toPrimaryKey(account, file.fileId!);
-
- static List