Fix AppDb.use returns before db is closed

This commit is contained in:
Ming Ming 2022-03-24 17:03:13 +08:00
parent 5554c32781
commit 0c23b8e7e4
16 changed files with 541 additions and 477 deletions

View file

@ -34,13 +34,19 @@ class AppDb {
/// 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<T> use<T>(FutureOr<T> Function(Database db) fn) async {
Future<T> use<T>(Transaction Function(Database db) transactionBuilder,
FutureOr<T> 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 {
return await fn(db);
transaction = transactionBuilder(db);
return await fn(transaction);
} finally {
if (transaction != null) {
await transaction.completed;
}
db.close();
}
});

View file

@ -398,8 +398,9 @@ class AlbumAppDbDataSource implements AlbumDataSource {
@override
get(Account account, File albumFile) {
_log.info("[get] ${albumFile.path}");
return appDb.use((db) async {
final transaction = db.transaction(AppDb.albumStoreName, idbModeReadOnly);
return appDb.use(
(db) => db.transaction(AppDb.albumStoreName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(AppDb.albumStoreName);
final index = store.index(AppDbAlbumEntry.indexName);
final path = AppDbAlbumEntry.toPathFromFile(account, albumFile);
@ -425,7 +426,8 @@ class AlbumAppDbDataSource implements AlbumDataSource {
} else {
throw CacheNotFoundException("No entry: $path");
}
});
},
);
}
@override
@ -437,12 +439,13 @@ class AlbumAppDbDataSource implements AlbumDataSource {
@override
update(Account account, Album album) {
_log.info("[update] ${album.albumFile!.path}");
return appDb.use((db) async {
final transaction =
db.transaction(AppDb.albumStoreName, idbModeReadWrite);
return appDb.use(
(db) => db.transaction(AppDb.albumStoreName, idbModeReadWrite),
(transaction) async {
final store = transaction.objectStore(AppDb.albumStoreName);
await _cacheAlbum(store, account, album);
});
},
);
}
@override
@ -492,9 +495,9 @@ class AlbumCachedDataSource implements AlbumDataSource {
@override
cleanUp(Account account, String rootDir, List<File> albumFiles) async {
appDb.use((db) async {
final transaction =
db.transaction(AppDb.albumStoreName, idbModeReadWrite);
appDb.use(
(db) => db.transaction(AppDb.albumStoreName, idbModeReadWrite),
(transaction) async {
final store = transaction.objectStore(AppDb.albumStoreName);
final index = store.index(AppDbAlbumEntry.indexName);
final rootPath = AppDbAlbumEntry.toPath(account, rootDir);
@ -505,8 +508,8 @@ class AlbumCachedDataSource implements AlbumDataSource {
.openKeyCursor(range: range, autoAdvance: true)
.map((cursor) => Tuple2((cursor.key as List)[0], cursor.primaryKey))
// and pick the dangling ones
.where((pair) => !albumFiles.any(
(f) => pair.item1 == AppDbAlbumEntry.toPathFromFile(account, f)))
.where((pair) => !albumFiles.any((f) =>
pair.item1 == AppDbAlbumEntry.toPathFromFile(account, f)))
// map to primary keys
.map((pair) => pair.item2)
.toList();
@ -519,16 +522,18 @@ class AlbumCachedDataSource implements AlbumDataSource {
"[cleanUp] Failed removing albumStore entry", e, stackTrace);
}
}
});
},
);
}
Future<void> _cacheResult(Account account, Album result) {
return appDb.use((db) async {
final transaction =
db.transaction(AppDb.albumStoreName, idbModeReadWrite);
return appDb.use(
(db) => db.transaction(AppDb.albumStoreName, idbModeReadWrite),
(transaction) async {
final store = transaction.objectStore(AppDb.albumStoreName);
await _cacheAlbum(store, account, result);
});
},
);
}
final AppDb appDb;

View file

@ -269,9 +269,10 @@ class FileAppDbDataSource implements FileDataSource {
@override
list(Account account, File dir) {
_log.info("[list] ${dir.path}");
return appDb.use((db) async {
final transaction = db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadOnly);
return appDb.use(
(db) => db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadOnly),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
final dirStore = transaction.objectStore(AppDb.dirStoreName);
final dirItem = await dirStore
@ -279,7 +280,8 @@ class FileAppDbDataSource implements FileDataSource {
if (dirItem == null) {
throw CacheNotFoundException("No entry: ${dir.path}");
}
final dirEntry = AppDbDirEntry.fromJson(dirItem.cast<String, dynamic>());
final dirEntry =
AppDbDirEntry.fromJson(dirItem.cast<String, dynamic>());
final entries = await Future.wait(dirEntry.children.map((c) async {
final fileItem = await fileStore
.getObject(AppDbFile2Entry.toPrimaryKey(account, c)) as Map?;
@ -293,7 +295,8 @@ class FileAppDbDataSource implements FileDataSource {
// we need to add dir to match the remote query
return [dirEntry.dir] +
entries.map((e) => e.file).where((f) => _validateFile(f)).toList();
});
},
);
}
@override
@ -307,8 +310,9 @@ class FileAppDbDataSource implements FileDataSource {
Future<List<File>> listByDate(
Account account, int fromEpochMs, int toEpochMs) async {
_log.info("[listByDate] [$fromEpochMs, $toEpochMs]");
final items = await appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
final items = await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
final dateTimeEpochMsIndex =
fileStore.index(AppDbFile2Entry.dateTimeEpochMsIndexName);
@ -319,7 +323,8 @@ class FileAppDbDataSource implements FileDataSource {
true,
);
return await dateTimeEpochMsIndex.getAll(range);
});
},
);
return items
.cast<Map>()
.map((i) => AppDbFile2Entry.fromJson(i.cast<String, dynamic>()))
@ -357,10 +362,9 @@ class FileAppDbDataSource implements FileDataSource {
bool? favorite,
}) {
_log.info("[updateProperty] ${f.path}");
return appDb.use((db) async {
final transaction =
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
return appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
(transaction) async {
// update file store
final newFile = f.copyWith(
metadata: metadata,
@ -371,7 +375,8 @@ class FileAppDbDataSource implements FileDataSource {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
await fileStore.put(AppDbFile2Entry.fromFile(account, newFile).toJson(),
AppDbFile2Entry.toPrimaryKeyForFile(account, newFile));
});
},
);
}
@override
@ -577,8 +582,9 @@ class FileForwardCacheManager {
}
Future<void> _cacheDir(Account account, File dir) async {
final dirItems = await appDb.use((db) async {
final transaction = db.transaction(AppDb.dirStoreName, idbModeReadOnly);
final dirItems = await appDb.use(
(db) => db.transaction(AppDb.dirStoreName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(AppDb.dirStoreName);
final dirItem = await store
.getObject(AppDbDirEntry.toPrimaryKeyForDir(account, dir)) as Map?;
@ -590,7 +596,8 @@ class FileForwardCacheManager {
AppDbDirEntry.toPrimaryUpperKeyForSubDirs(account, dir),
);
return [dirItem] + (await store.getAll(range)).cast<Map>();
});
},
);
if (dirItems == null) {
// no cache
return;
@ -606,12 +613,14 @@ class FileForwardCacheManager {
// cache files
final fileIds = dirs.map((e) => e.children).fold<List<int>>(
[], (previousValue, element) => previousValue + element);
final fileItems = await appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
final fileItems = await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(AppDb.file2StoreName);
return await Future.wait(fileIds.map(
(id) => store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
});
return await Future.wait(fileIds.map((id) =>
store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
},
);
final files = fileItems
.cast<Map?>()
.whereType<Map>()

View file

@ -124,9 +124,10 @@ class FileCacheUpdater {
}
Future<void> _cacheRemote(Account account, File dir, List<File> remote) {
return appDb.use((db) async {
final transaction = db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite);
return appDb.use(
(db) => db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite),
(transaction) async {
final dirStore = transaction.objectStore(AppDb.dirStoreName);
final fileStore = transaction.objectStore(AppDb.file2StoreName);
@ -142,9 +143,11 @@ class FileCacheUpdater {
final remoteChildren = resultGroup[false] ?? [];
// add dir to db
await dirStore.put(
AppDbDirEntry.fromFiles(account, remoteDir, remoteChildren).toJson(),
AppDbDirEntry.fromFiles(account, remoteDir, remoteChildren)
.toJson(),
AppDbDirEntry.toPrimaryKeyForDir(account, remoteDir));
});
},
);
}
/// Remove extra entries from local cache based on remote contents
@ -159,9 +162,10 @@ class FileCacheUpdater {
_log.info(
"[_cleanUpCache] Removed: ${removed.map((f) => f.path).toReadableString()}");
await appDb.use((db) async {
final transaction = db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite);
await appDb.use(
(db) => db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite),
(transaction) async {
final dirStore = transaction.objectStore(AppDb.dirStoreName);
final fileStore = transaction.objectStore(AppDb.file2StoreName);
for (final f in removed) {
@ -179,7 +183,8 @@ class FileCacheUpdater {
stackTrace);
}
}
});
},
);
}
final AppDb appDb;
@ -198,23 +203,28 @@ class FileCacheRemover {
/// If [f] is a file, the file will be removed from file2Store, but no changes
/// to dirStore.
Future<void> call(Account account, File f) async {
await appDb.use((db) async {
if (f.isCollection != false) {
// removing dir is basically a superset of removing file, so we'll treat
// unspecified file as dir
final transaction = db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite);
await appDb.use(
(db) => db.transaction(
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite),
(transaction) async {
final dirStore = transaction.objectStore(AppDb.dirStoreName);
final fileStore = transaction.objectStore(AppDb.file2StoreName);
await _removeDirFromAppDb(account, f,
dirStore: dirStore, fileStore: fileStore);
},
);
} else {
final transaction =
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
await _removeFileFromAppDb(account, f, fileStore: fileStore);
},
);
}
});
}
final AppDb appDb;

View file

@ -37,9 +37,9 @@ class CacheFavorite {
if (newFavorites.isEmpty && removedFavorites.isEmpty) {
return;
}
await _c.appDb.use((db) async {
final transaction =
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
await _c.appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
await Future.wait(newFavorites.map((f) async {
_log.info("[call] New favorite: ${f.path}");
@ -65,7 +65,8 @@ class CacheFavorite {
stackTrace);
}
}));
});
},
);
KiwiContainer()
.resolve<EventBus>()

View file

@ -12,14 +12,15 @@ class CompatV37 {
static Future<void> setAppDbMigrationFlag(AppDb appDb) async {
_log.info("[setAppDbMigrationFlag] Set db flag");
try {
await appDb.use((db) async {
final transaction =
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
await metaStore
.put(const AppDbMetaEntryCompatV37(false).toEntry().toJson());
await transaction.completed;
});
},
);
} catch (e, stackTrace) {
_log.shout(
"[setAppDbMigrationFlag] Failed while setting db flag, drop db instead",
@ -30,11 +31,13 @@ class CompatV37 {
}
static Future<bool> isAppDbNeedMigration(AppDb appDb) async {
final dbItem = await appDb.use((db) async {
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadOnly);
final dbItem = await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadOnly),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
return await metaStore.getObject(AppDbMetaEntryCompatV37.key) as Map?;
});
},
);
if (dbItem == null) {
return false;
}
@ -51,10 +54,11 @@ class CompatV37 {
static Future<void> migrateAppDb(AppDb appDb) async {
_log.info("[migrateAppDb] Migrate AppDb");
try {
await appDb.use((db) async {
final transaction = db.transaction(
await appDb.use(
(db) => db.transaction(
[AppDb.file2StoreName, AppDb.dirStoreName, AppDb.metaStoreName],
idbModeReadWrite);
idbModeReadWrite),
(transaction) async {
final noMediaFiles = <_NoMediaFile>[];
try {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
@ -95,7 +99,8 @@ class CompatV37 {
transaction.abort();
rethrow;
}
});
},
);
} catch (e, stackTrace) {
_log.shout("[migrateAppDb] Failed while migrating, drop db instead", e,
stackTrace);

View file

@ -7,11 +7,13 @@ import 'package:nc_photos/object_extension.dart';
class DbCompatV5 {
static Future<bool> isNeedMigration(AppDb appDb) async {
final dbItem = await appDb.use((db) async {
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadOnly);
final dbItem = await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadOnly),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
return await metaStore.getObject(AppDbMetaEntryDbCompatV5.key) as Map?;
});
},
);
if (dbItem == null) {
return false;
}
@ -28,9 +30,10 @@ class DbCompatV5 {
static Future<void> migrate(AppDb appDb) async {
_log.info("[migrate] Migrate AppDb");
try {
await appDb.use((db) async {
final transaction = db.transaction(
[AppDb.file2StoreName, AppDb.metaStoreName], idbModeReadWrite);
await appDb.use(
(db) => db.transaction(
[AppDb.file2StoreName, AppDb.metaStoreName], idbModeReadWrite),
(transaction) async {
try {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
await for (final c in fileStore.openCursor()) {
@ -57,7 +60,8 @@ class DbCompatV5 {
transaction.abort();
rethrow;
}
});
},
);
} catch (e, stackTrace) {
_log.shout(
"[migrate] Failed while migrating, drop db instead", e, stackTrace);

View file

@ -19,12 +19,14 @@ class FindFile {
List<int> fileIds, {
void Function(int fileId)? onFileNotFound,
}) async {
final dbItems = await _c.appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
final dbItems = await _c.appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
return await Future.wait(fileIds.map((id) =>
fileStore.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
});
},
);
final files = <File>[];
for (final pair in zip([fileIds, dbItems])) {
final dbItem = pair[1] as Map?;

View file

@ -18,8 +18,9 @@ class ListFavoriteOffline {
final rootDirs = account.roots
.map((r) => File(path: file_util.unstripPath(account, r)))
.toList();
return _c.appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
return _c.appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
final fileIsFavoriteIndex =
fileStore.index(AppDbFile2Entry.fileIsFavoriteIndexName);
@ -35,7 +36,8 @@ class ListFavoriteOffline {
file_util.isSupportedFormat(f) &&
rootDirs.any((r) => file_util.isOrUnderDir(f, r)))
.toList();
});
},
);
}
final DiContainer _c;

View file

@ -11,8 +11,9 @@ class PopulatePerson {
/// Return a list of files of the faces
Future<List<File>> call(Account account, List<Face> faces) async {
return await appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
return await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(AppDb.file2StoreName);
final products = <File>[];
for (final f in faces) {
@ -24,7 +25,8 @@ class PopulatePerson {
}
}
return products;
});
},
);
}
Future<File> _populateOne(

View file

@ -20,8 +20,9 @@ class ResyncAlbum {
final items = AlbumStaticProvider.of(album).items;
final fileIds =
items.whereType<AlbumFileItem>().map((i) => i.file.fileId!).toList();
final dbItems = Map.fromEntries(await appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
final dbItems = Map.fromEntries(await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(AppDb.file2StoreName);
return await Future.wait(fileIds.map(
(id) async => MapEntry(
@ -30,7 +31,8 @@ class ResyncAlbum {
as Map?,
),
));
}));
},
));
return items.map((i) {
if (i is AlbumFileItem) {
try {

View file

@ -12,8 +12,9 @@ class ScanDirOffline {
/// List all files under a dir recursively from the local DB
Future<List<File>> call(Account account, File root) async {
return await _c.appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
return await _c.appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(AppDb.file2StoreName);
final index = store.index(AppDbFile2Entry.strippedPathIndexName);
final range = KeyRange.bound(
@ -24,10 +25,12 @@ class ScanDirOffline {
.openCursor(range: range, autoAdvance: true)
.map((c) => c.value)
.cast<Map>()
.map((e) => AppDbFile2Entry.fromJson(e.cast<String, dynamic>()).file)
.map(
(e) => AppDbFile2Entry.fromJson(e.cast<String, dynamic>()).file)
.where((f) => file_util.isSupportedFormat(f))
.toList();
});
},
);
}
final DiContainer _c;

View file

@ -100,7 +100,8 @@ class MockAppDb implements AppDb {
}
@override
Future<T> use<T>(FutureOr<T> Function(Database db) fn) async {
Future<T> use<T>(Transaction Function(Database db) transactionBuilder,
FutureOr<T> Function(Transaction transaction) fn) async {
final db = await _dbFactory.open(
"test.db",
version: 1,
@ -110,9 +111,14 @@ class MockAppDb implements AppDb {
},
);
Transaction? transaction;
try {
return await fn(db);
transaction = transactionBuilder(db);
return await fn(transaction);
} finally {
if (transaction != null) {
await transaction.completed;
}
db.close();
}
}

View file

@ -341,30 +341,36 @@ Sharee buildSharee({
Future<void> fillAppDb(
AppDb appDb, Account account, Iterable<File> files) async {
await appDb.use((db) async {
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
(transaction) async {
final file2Store = transaction.objectStore(AppDb.file2StoreName);
for (final f in files) {
await file2Store.put(AppDbFile2Entry.fromFile(account, f).toJson(),
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
}
});
},
);
}
Future<void> fillAppDbDir(
AppDb appDb, Account account, File dir, List<File> children) async {
await appDb.use((db) async {
final transaction = db.transaction(AppDb.dirStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.dirStoreName, idbModeReadWrite),
(transaction) async {
final dirStore = transaction.objectStore(AppDb.dirStoreName);
await dirStore.put(AppDbDirEntry.fromFiles(account, dir, children).toJson(),
await dirStore.put(
AppDbDirEntry.fromFiles(account, dir, children).toJson(),
AppDbDirEntry.toPrimaryKeyForDir(account, dir));
});
},
);
}
Future<List<T>> listAppDb<T>(
AppDb appDb, String storeName, T Function(JsonObj) transform) {
return appDb.use((db) async {
final transaction = db.transaction(storeName, idbModeReadOnly);
return appDb.use(
(db) => db.transaction(storeName, idbModeReadOnly),
(transaction) async {
final store = transaction.objectStore(storeName);
return await store
.openCursor(autoAdvance: true)
@ -372,5 +378,6 @@ Future<List<T>> listAppDb<T>(
.cast<Map>()
.map((e) => transform(e.cast<String, dynamic>()))
.toList();
});
},
);
}

View file

@ -29,12 +29,14 @@ void main() {
/// Expect: true
Future<void> _isAppDbNeedMigrationEntryFalse() async {
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
const entry = AppDbMetaEntryCompatV37(false);
await metaStore.put(entry.toEntry().toJson());
});
},
);
expect(await CompatV37.isAppDbNeedMigration(appDb), true);
}
@ -44,12 +46,14 @@ Future<void> _isAppDbNeedMigrationEntryFalse() async {
/// Expect: false
Future<void> _isAppDbNeedMigrationEntryTrue() async {
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
const entry = AppDbMetaEntryCompatV37(true);
await metaStore.put(entry.toEntry().toJson());
});
},
);
expect(await CompatV37.isAppDbNeedMigration(appDb), false);
}
@ -59,12 +63,14 @@ Future<void> _isAppDbNeedMigrationEntryTrue() async {
/// Expect: false
Future<void> _isAppDbNeedMigrationWithoutEntry() async {
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
const entry = AppDbMetaEntryCompatV37(true);
await metaStore.put(entry.toEntry().toJson());
});
},
);
expect(await CompatV37.isAppDbNeedMigration(appDb), false);
}
@ -81,11 +87,9 @@ Future<void> _migrateAppDbWithoutNomedia() async {
..addJpeg("admin/dir1/test2.jpg"))
.build();
final appDb = MockAppDb();
await appDb.use((db) async {
await util.fillAppDb(appDb, account, files);
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
await util.fillAppDbDir(appDb, account, files[2], [files[3]]);
});
await CompatV37.migrateAppDb(appDb);
final fileObjs = await util.listAppDb(
@ -113,11 +117,9 @@ Future<void> _migrateAppDb() async {
..addJpeg("admin/dir1/test2.jpg"))
.build();
final appDb = MockAppDb();
await appDb.use((db) async {
await util.fillAppDb(appDb, account, files);
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
await util.fillAppDbDir(appDb, account, files[2], files.slice(3, 5));
});
await CompatV37.migrateAppDb(appDb);
final fileObjs = await util.listAppDb(
@ -147,12 +149,10 @@ Future<void> _migrateAppDbNestedDir() async {
..addJpeg("admin/dir1/dir1-1/test3.jpg"))
.build();
final appDb = MockAppDb();
await appDb.use((db) async {
await util.fillAppDb(appDb, account, files);
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
await util.fillAppDbDir(appDb, account, files[2], files.slice(3, 6));
await util.fillAppDbDir(appDb, account, files[5], [files[6]]);
});
await CompatV37.migrateAppDb(appDb);
final fileObjs = await util.listAppDb(
@ -183,12 +183,10 @@ Future<void> _migrateAppDbNestedMarker() async {
..addJpeg("admin/dir1/dir1-1/test3.jpg"))
.build();
final appDb = MockAppDb();
await appDb.use((db) async {
await util.fillAppDb(appDb, account, files);
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
await util.fillAppDbDir(appDb, account, files[2], files.slice(3, 6));
await util.fillAppDbDir(appDb, account, files[5], files.slice(6, 8));
});
await CompatV37.migrateAppDb(appDb);
final fileObjs = await util.listAppDb(
@ -216,11 +214,9 @@ Future<void> _migrateAppDbRoot() async {
..addJpeg("admin/dir1/test2.jpg"))
.build();
final appDb = MockAppDb();
await appDb.use((db) async {
await util.fillAppDb(appDb, account, files);
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 4));
await util.fillAppDbDir(appDb, account, files[3], [files[4]]);
});
await CompatV37.migrateAppDb(appDb);
final objs = await util.listAppDb(

View file

@ -13,39 +13,42 @@ void main() {
group("isNeedMigration", () {
test("w/ meta entry == false", () async {
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction =
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
const entry = AppDbMetaEntryDbCompatV5(false);
await metaStore.put(entry.toEntry().toJson());
});
},
);
expect(await DbCompatV5.isNeedMigration(appDb), true);
});
test("w/ meta entry == true", () async {
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction =
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
const entry = AppDbMetaEntryDbCompatV5(true);
await metaStore.put(entry.toEntry().toJson());
});
},
);
expect(await DbCompatV5.isNeedMigration(appDb), false);
});
test("w/o meta entry", () async {
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction =
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
(transaction) async {
final metaStore = transaction.objectStore(AppDb.metaStoreName);
const entry = AppDbMetaEntryDbCompatV5(true);
await metaStore.put(entry.toEntry().toJson());
});
},
);
expect(await DbCompatV5.isNeedMigration(appDb), false);
});
@ -60,9 +63,9 @@ void main() {
))
.build();
final appDb = MockAppDb();
await appDb.use((db) async {
final transaction =
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
await appDb.use(
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
(transaction) async {
final fileStore = transaction.objectStore(AppDb.file2StoreName);
await fileStore.put({
"server": account.url,
@ -70,7 +73,8 @@ void main() {
"strippedPath": files[0].strippedPathWithEmpty,
"file": files[0].toJson(),
}, "${account.url}/${account.username.toCaseInsensitiveString()}/${files[0].fileId}");
});
},
);
await DbCompatV5.migrate(appDb);
final objs =