mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Fix AppDb.use returns before db is closed
This commit is contained in:
parent
5554c32781
commit
0c23b8e7e4
16 changed files with 541 additions and 477 deletions
|
@ -34,13 +34,19 @@ class AppDb {
|
||||||
/// This function guarantees that:
|
/// This function guarantees that:
|
||||||
/// 1) Database is always closed after [fn] exits, even with an error
|
/// 1) Database is always closed after [fn] exits, even with an error
|
||||||
/// 2) Only at most 1 database instance being opened at any time
|
/// 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
|
// make sure only one client is opening the db
|
||||||
return await platform.Lock.synchronized(k.appDbLockId, () async {
|
return await platform.Lock.synchronized(k.appDbLockId, () async {
|
||||||
final db = await _open();
|
final db = await _open();
|
||||||
|
Transaction? transaction;
|
||||||
try {
|
try {
|
||||||
return await fn(db);
|
transaction = transactionBuilder(db);
|
||||||
|
return await fn(transaction);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (transaction != null) {
|
||||||
|
await transaction.completed;
|
||||||
|
}
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -398,34 +398,36 @@ class AlbumAppDbDataSource implements AlbumDataSource {
|
||||||
@override
|
@override
|
||||||
get(Account account, File albumFile) {
|
get(Account account, File albumFile) {
|
||||||
_log.info("[get] ${albumFile.path}");
|
_log.info("[get] ${albumFile.path}");
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction = db.transaction(AppDb.albumStoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.albumStoreName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(AppDb.albumStoreName);
|
(transaction) async {
|
||||||
final index = store.index(AppDbAlbumEntry.indexName);
|
final store = transaction.objectStore(AppDb.albumStoreName);
|
||||||
final path = AppDbAlbumEntry.toPathFromFile(account, albumFile);
|
final index = store.index(AppDbAlbumEntry.indexName);
|
||||||
final range = KeyRange.bound([path, 0], [path, int_util.int32Max]);
|
final path = AppDbAlbumEntry.toPathFromFile(account, albumFile);
|
||||||
final List results = await index.getAll(range);
|
final range = KeyRange.bound([path, 0], [path, int_util.int32Max]);
|
||||||
if (results.isNotEmpty == true) {
|
final List results = await index.getAll(range);
|
||||||
final entries = results.map((e) =>
|
if (results.isNotEmpty == true) {
|
||||||
AppDbAlbumEntry.fromJson(e.cast<String, dynamic>(), account));
|
final entries = results.map((e) =>
|
||||||
if (entries.length > 1) {
|
AppDbAlbumEntry.fromJson(e.cast<String, dynamic>(), account));
|
||||||
final items = entries.map((e) {
|
if (entries.length > 1) {
|
||||||
_log.info("[get] ${e.path}[${e.index}]");
|
final items = entries.map((e) {
|
||||||
return AlbumStaticProvider.of(e.album).items;
|
_log.info("[get] ${e.path}[${e.index}]");
|
||||||
}).reduce((value, element) => value + element);
|
return AlbumStaticProvider.of(e.album).items;
|
||||||
return entries.first.album.copyWith(
|
}).reduce((value, element) => value + element);
|
||||||
lastUpdated: OrNull(null),
|
return entries.first.album.copyWith(
|
||||||
provider: AlbumStaticProvider.of(entries.first.album).copyWith(
|
lastUpdated: OrNull(null),
|
||||||
items: items,
|
provider: AlbumStaticProvider.of(entries.first.album).copyWith(
|
||||||
),
|
items: items,
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return entries.first.album;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return entries.first.album;
|
throw CacheNotFoundException("No entry: $path");
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
throw CacheNotFoundException("No entry: $path");
|
);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -437,12 +439,13 @@ class AlbumAppDbDataSource implements AlbumDataSource {
|
||||||
@override
|
@override
|
||||||
update(Account account, Album album) {
|
update(Account account, Album album) {
|
||||||
_log.info("[update] ${album.albumFile!.path}");
|
_log.info("[update] ${album.albumFile!.path}");
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.albumStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.albumStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final store = transaction.objectStore(AppDb.albumStoreName);
|
final store = transaction.objectStore(AppDb.albumStoreName);
|
||||||
await _cacheAlbum(store, account, album);
|
await _cacheAlbum(store, account, album);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -492,43 +495,45 @@ class AlbumCachedDataSource implements AlbumDataSource {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
cleanUp(Account account, String rootDir, List<File> albumFiles) async {
|
cleanUp(Account account, String rootDir, List<File> albumFiles) async {
|
||||||
appDb.use((db) async {
|
appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.albumStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.albumStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final store = transaction.objectStore(AppDb.albumStoreName);
|
final store = transaction.objectStore(AppDb.albumStoreName);
|
||||||
final index = store.index(AppDbAlbumEntry.indexName);
|
final index = store.index(AppDbAlbumEntry.indexName);
|
||||||
final rootPath = AppDbAlbumEntry.toPath(account, rootDir);
|
final rootPath = AppDbAlbumEntry.toPath(account, rootDir);
|
||||||
final range = KeyRange.bound(
|
final range = KeyRange.bound(
|
||||||
["$rootPath/", 0], ["$rootPath/\uffff", int_util.int32Max]);
|
["$rootPath/", 0], ["$rootPath/\uffff", int_util.int32Max]);
|
||||||
final danglingKeys = await index
|
final danglingKeys = await index
|
||||||
// get all albums for this account
|
// get all albums for this account
|
||||||
.openKeyCursor(range: range, autoAdvance: true)
|
.openKeyCursor(range: range, autoAdvance: true)
|
||||||
.map((cursor) => Tuple2((cursor.key as List)[0], cursor.primaryKey))
|
.map((cursor) => Tuple2((cursor.key as List)[0], cursor.primaryKey))
|
||||||
// and pick the dangling ones
|
// and pick the dangling ones
|
||||||
.where((pair) => !albumFiles.any(
|
.where((pair) => !albumFiles.any((f) =>
|
||||||
(f) => pair.item1 == AppDbAlbumEntry.toPathFromFile(account, f)))
|
pair.item1 == AppDbAlbumEntry.toPathFromFile(account, f)))
|
||||||
// map to primary keys
|
// map to primary keys
|
||||||
.map((pair) => pair.item2)
|
.map((pair) => pair.item2)
|
||||||
.toList();
|
.toList();
|
||||||
for (final k in danglingKeys) {
|
for (final k in danglingKeys) {
|
||||||
_log.fine("[cleanUp] Removing albumStore entry: $k");
|
_log.fine("[cleanUp] Removing albumStore entry: $k");
|
||||||
try {
|
try {
|
||||||
await store.delete(k);
|
await store.delete(k);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[cleanUp] Failed removing albumStore entry", e, stackTrace);
|
"[cleanUp] Failed removing albumStore entry", e, stackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _cacheResult(Account account, Album result) {
|
Future<void> _cacheResult(Account account, Album result) {
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.albumStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.albumStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final store = transaction.objectStore(AppDb.albumStoreName);
|
final store = transaction.objectStore(AppDb.albumStoreName);
|
||||||
await _cacheAlbum(store, account, result);
|
await _cacheAlbum(store, account, result);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AppDb appDb;
|
final AppDb appDb;
|
||||||
|
|
|
@ -269,31 +269,34 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
@override
|
@override
|
||||||
list(Account account, File dir) {
|
list(Account account, File dir) {
|
||||||
_log.info("[list] ${dir.path}");
|
_log.info("[list] ${dir.path}");
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction = db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadOnly);
|
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadOnly),
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
final dirItem = await dirStore
|
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
||||||
.getObject(AppDbDirEntry.toPrimaryKeyForDir(account, dir)) as Map?;
|
final dirItem = await dirStore
|
||||||
if (dirItem == null) {
|
.getObject(AppDbDirEntry.toPrimaryKeyForDir(account, dir)) as Map?;
|
||||||
throw CacheNotFoundException("No entry: ${dir.path}");
|
if (dirItem == null) {
|
||||||
}
|
throw CacheNotFoundException("No entry: ${dir.path}");
|
||||||
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?;
|
|
||||||
if (fileItem == null) {
|
|
||||||
_log.warning(
|
|
||||||
"[list] Missing file ($c) in db for dir: ${logFilename(dir.path)}");
|
|
||||||
throw CacheNotFoundException("No entry for dir child: $c");
|
|
||||||
}
|
}
|
||||||
return AppDbFile2Entry.fromJson(fileItem.cast<String, dynamic>());
|
final dirEntry =
|
||||||
}));
|
AppDbDirEntry.fromJson(dirItem.cast<String, dynamic>());
|
||||||
// we need to add dir to match the remote query
|
final entries = await Future.wait(dirEntry.children.map((c) async {
|
||||||
return [dirEntry.dir] +
|
final fileItem = await fileStore
|
||||||
entries.map((e) => e.file).where((f) => _validateFile(f)).toList();
|
.getObject(AppDbFile2Entry.toPrimaryKey(account, c)) as Map?;
|
||||||
});
|
if (fileItem == null) {
|
||||||
|
_log.warning(
|
||||||
|
"[list] Missing file ($c) in db for dir: ${logFilename(dir.path)}");
|
||||||
|
throw CacheNotFoundException("No entry for dir child: $c");
|
||||||
|
}
|
||||||
|
return AppDbFile2Entry.fromJson(fileItem.cast<String, dynamic>());
|
||||||
|
}));
|
||||||
|
// we need to add dir to match the remote query
|
||||||
|
return [dirEntry.dir] +
|
||||||
|
entries.map((e) => e.file).where((f) => _validateFile(f)).toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -307,19 +310,21 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
Future<List<File>> listByDate(
|
Future<List<File>> listByDate(
|
||||||
Account account, int fromEpochMs, int toEpochMs) async {
|
Account account, int fromEpochMs, int toEpochMs) async {
|
||||||
_log.info("[listByDate] [$fromEpochMs, $toEpochMs]");
|
_log.info("[listByDate] [$fromEpochMs, $toEpochMs]");
|
||||||
final items = await appDb.use((db) async {
|
final items = await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
final dateTimeEpochMsIndex =
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
fileStore.index(AppDbFile2Entry.dateTimeEpochMsIndexName);
|
final dateTimeEpochMsIndex =
|
||||||
final range = KeyRange.bound(
|
fileStore.index(AppDbFile2Entry.dateTimeEpochMsIndexName);
|
||||||
AppDbFile2Entry.toDateTimeEpochMsIndexKey(account, fromEpochMs),
|
final range = KeyRange.bound(
|
||||||
AppDbFile2Entry.toDateTimeEpochMsIndexKey(account, toEpochMs),
|
AppDbFile2Entry.toDateTimeEpochMsIndexKey(account, fromEpochMs),
|
||||||
false,
|
AppDbFile2Entry.toDateTimeEpochMsIndexKey(account, toEpochMs),
|
||||||
true,
|
false,
|
||||||
);
|
true,
|
||||||
return await dateTimeEpochMsIndex.getAll(range);
|
);
|
||||||
});
|
return await dateTimeEpochMsIndex.getAll(range);
|
||||||
|
},
|
||||||
|
);
|
||||||
return items
|
return items
|
||||||
.cast<Map>()
|
.cast<Map>()
|
||||||
.map((i) => AppDbFile2Entry.fromJson(i.cast<String, dynamic>()))
|
.map((i) => AppDbFile2Entry.fromJson(i.cast<String, dynamic>()))
|
||||||
|
@ -357,21 +362,21 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
bool? favorite,
|
bool? favorite,
|
||||||
}) {
|
}) {
|
||||||
_log.info("[updateProperty] ${f.path}");
|
_log.info("[updateProperty] ${f.path}");
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
|
// update file store
|
||||||
// update file store
|
final newFile = f.copyWith(
|
||||||
final newFile = f.copyWith(
|
metadata: metadata,
|
||||||
metadata: metadata,
|
isArchived: isArchived,
|
||||||
isArchived: isArchived,
|
overrideDateTime: overrideDateTime,
|
||||||
overrideDateTime: overrideDateTime,
|
isFavorite: favorite,
|
||||||
isFavorite: favorite,
|
);
|
||||||
);
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
await fileStore.put(AppDbFile2Entry.fromFile(account, newFile).toJson(),
|
||||||
await fileStore.put(AppDbFile2Entry.fromFile(account, newFile).toJson(),
|
AppDbFile2Entry.toPrimaryKeyForFile(account, newFile));
|
||||||
AppDbFile2Entry.toPrimaryKeyForFile(account, newFile));
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -577,20 +582,22 @@ class FileForwardCacheManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _cacheDir(Account account, File dir) async {
|
Future<void> _cacheDir(Account account, File dir) async {
|
||||||
final dirItems = await appDb.use((db) async {
|
final dirItems = await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.dirStoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.dirStoreName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(AppDb.dirStoreName);
|
(transaction) async {
|
||||||
final dirItem = await store
|
final store = transaction.objectStore(AppDb.dirStoreName);
|
||||||
.getObject(AppDbDirEntry.toPrimaryKeyForDir(account, dir)) as Map?;
|
final dirItem = await store
|
||||||
if (dirItem == null) {
|
.getObject(AppDbDirEntry.toPrimaryKeyForDir(account, dir)) as Map?;
|
||||||
return null;
|
if (dirItem == null) {
|
||||||
}
|
return null;
|
||||||
final range = KeyRange.bound(
|
}
|
||||||
AppDbDirEntry.toPrimaryLowerKeyForSubDirs(account, dir),
|
final range = KeyRange.bound(
|
||||||
AppDbDirEntry.toPrimaryUpperKeyForSubDirs(account, dir),
|
AppDbDirEntry.toPrimaryLowerKeyForSubDirs(account, dir),
|
||||||
);
|
AppDbDirEntry.toPrimaryUpperKeyForSubDirs(account, dir),
|
||||||
return [dirItem] + (await store.getAll(range)).cast<Map>();
|
);
|
||||||
});
|
return [dirItem] + (await store.getAll(range)).cast<Map>();
|
||||||
|
},
|
||||||
|
);
|
||||||
if (dirItems == null) {
|
if (dirItems == null) {
|
||||||
// no cache
|
// no cache
|
||||||
return;
|
return;
|
||||||
|
@ -606,12 +613,14 @@ class FileForwardCacheManager {
|
||||||
// cache files
|
// cache files
|
||||||
final fileIds = dirs.map((e) => e.children).fold<List<int>>(
|
final fileIds = dirs.map((e) => e.children).fold<List<int>>(
|
||||||
[], (previousValue, element) => previousValue + element);
|
[], (previousValue, element) => previousValue + element);
|
||||||
final fileItems = await appDb.use((db) async {
|
final fileItems = await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
return await Future.wait(fileIds.map(
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
(id) => store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
|
return await Future.wait(fileIds.map((id) =>
|
||||||
});
|
store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
|
||||||
|
},
|
||||||
|
);
|
||||||
final files = fileItems
|
final files = fileItems
|
||||||
.cast<Map?>()
|
.cast<Map?>()
|
||||||
.whereType<Map>()
|
.whereType<Map>()
|
||||||
|
|
|
@ -124,27 +124,30 @@ class FileCacheUpdater {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _cacheRemote(Account account, File dir, List<File> remote) {
|
Future<void> _cacheRemote(Account account, File dir, List<File> remote) {
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction = db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite);
|
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite),
|
||||||
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
||||||
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
|
|
||||||
// add files to db
|
// add files to db
|
||||||
await Future.wait(remote.map((f) => fileStore.put(
|
await Future.wait(remote.map((f) => fileStore.put(
|
||||||
AppDbFile2Entry.fromFile(account, f).toJson(),
|
AppDbFile2Entry.fromFile(account, f).toJson(),
|
||||||
AppDbFile2Entry.toPrimaryKeyForFile(account, f))));
|
AppDbFile2Entry.toPrimaryKeyForFile(account, f))));
|
||||||
|
|
||||||
// results from remote also contain the dir itself
|
// results from remote also contain the dir itself
|
||||||
final resultGroup =
|
final resultGroup =
|
||||||
remote.groupListsBy((f) => f.compareServerIdentity(dir));
|
remote.groupListsBy((f) => f.compareServerIdentity(dir));
|
||||||
final remoteDir = resultGroup[true]!.first;
|
final remoteDir = resultGroup[true]!.first;
|
||||||
final remoteChildren = resultGroup[false] ?? [];
|
final remoteChildren = resultGroup[false] ?? [];
|
||||||
// add dir to db
|
// add dir to db
|
||||||
await dirStore.put(
|
await dirStore.put(
|
||||||
AppDbDirEntry.fromFiles(account, remoteDir, remoteChildren).toJson(),
|
AppDbDirEntry.fromFiles(account, remoteDir, remoteChildren)
|
||||||
AppDbDirEntry.toPrimaryKeyForDir(account, remoteDir));
|
.toJson(),
|
||||||
});
|
AppDbDirEntry.toPrimaryKeyForDir(account, remoteDir));
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove extra entries from local cache based on remote contents
|
/// Remove extra entries from local cache based on remote contents
|
||||||
|
@ -159,27 +162,29 @@ class FileCacheUpdater {
|
||||||
_log.info(
|
_log.info(
|
||||||
"[_cleanUpCache] Removed: ${removed.map((f) => f.path).toReadableString()}");
|
"[_cleanUpCache] Removed: ${removed.map((f) => f.path).toReadableString()}");
|
||||||
|
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite);
|
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite),
|
||||||
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
||||||
for (final f in removed) {
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
try {
|
for (final f in removed) {
|
||||||
if (f.isCollection == true) {
|
try {
|
||||||
await _removeDirFromAppDb(account, f,
|
if (f.isCollection == true) {
|
||||||
dirStore: dirStore, fileStore: fileStore);
|
await _removeDirFromAppDb(account, f,
|
||||||
} else {
|
dirStore: dirStore, fileStore: fileStore);
|
||||||
await _removeFileFromAppDb(account, f, fileStore: fileStore);
|
} else {
|
||||||
|
await _removeFileFromAppDb(account, f, fileStore: fileStore);
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.shout(
|
||||||
|
"[_cleanUpCache] Failed while removing file: ${logFilename(f.path)}",
|
||||||
|
e,
|
||||||
|
stackTrace);
|
||||||
}
|
}
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout(
|
|
||||||
"[_cleanUpCache] Failed while removing file: ${logFilename(f.path)}",
|
|
||||||
e,
|
|
||||||
stackTrace);
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final AppDb appDb;
|
final AppDb appDb;
|
||||||
|
@ -198,23 +203,28 @@ class FileCacheRemover {
|
||||||
/// If [f] is a file, the file will be removed from file2Store, but no changes
|
/// If [f] is a file, the file will be removed from file2Store, but no changes
|
||||||
/// to dirStore.
|
/// to dirStore.
|
||||||
Future<void> call(Account account, File f) async {
|
Future<void> call(Account account, File f) async {
|
||||||
await appDb.use((db) async {
|
if (f.isCollection != false) {
|
||||||
if (f.isCollection != false) {
|
// removing dir is basically a superset of removing file, so we'll treat
|
||||||
// removing dir is basically a superset of removing file, so we'll treat
|
// unspecified file as dir
|
||||||
// unspecified file as dir
|
await appDb.use(
|
||||||
final transaction = db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite);
|
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadWrite),
|
||||||
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
||||||
await _removeDirFromAppDb(account, f,
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
dirStore: dirStore, fileStore: fileStore);
|
await _removeDirFromAppDb(account, f,
|
||||||
} else {
|
dirStore: dirStore, fileStore: fileStore);
|
||||||
final transaction =
|
},
|
||||||
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
|
);
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
} else {
|
||||||
await _removeFileFromAppDb(account, f, fileStore: fileStore);
|
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;
|
final AppDb appDb;
|
||||||
|
|
|
@ -37,35 +37,36 @@ class CacheFavorite {
|
||||||
if (newFavorites.isEmpty && removedFavorites.isEmpty) {
|
if (newFavorites.isEmpty && removedFavorites.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await _c.appDb.use((db) async {
|
await _c.appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
await Future.wait(newFavorites.map((f) async {
|
await Future.wait(newFavorites.map((f) async {
|
||||||
_log.info("[call] New favorite: ${f.path}");
|
_log.info("[call] New favorite: ${f.path}");
|
||||||
try {
|
try {
|
||||||
await fileStore.put(AppDbFile2Entry.fromFile(account, f).toJson(),
|
await fileStore.put(AppDbFile2Entry.fromFile(account, f).toJson(),
|
||||||
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
|
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[call] Failed while writing new favorite to AppDb: ${logFilename(f.path)}",
|
"[call] Failed while writing new favorite to AppDb: ${logFilename(f.path)}",
|
||||||
e,
|
e,
|
||||||
stackTrace);
|
stackTrace);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
await Future.wait(removedFavorites.map((f) async {
|
await Future.wait(removedFavorites.map((f) async {
|
||||||
_log.info("[call] Remove favorite: ${f.path}");
|
_log.info("[call] Remove favorite: ${f.path}");
|
||||||
try {
|
try {
|
||||||
await fileStore.put(AppDbFile2Entry.fromFile(account, f).toJson(),
|
await fileStore.put(AppDbFile2Entry.fromFile(account, f).toJson(),
|
||||||
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
|
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[call] Failed while writing removed favorite to AppDb: ${logFilename(f.path)}",
|
"[call] Failed while writing removed favorite to AppDb: ${logFilename(f.path)}",
|
||||||
e,
|
e,
|
||||||
stackTrace);
|
stackTrace);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
KiwiContainer()
|
KiwiContainer()
|
||||||
.resolve<EventBus>()
|
.resolve<EventBus>()
|
||||||
|
|
|
@ -12,14 +12,15 @@ class CompatV37 {
|
||||||
static Future<void> setAppDbMigrationFlag(AppDb appDb) async {
|
static Future<void> setAppDbMigrationFlag(AppDb appDb) async {
|
||||||
_log.info("[setAppDbMigrationFlag] Set db flag");
|
_log.info("[setAppDbMigrationFlag] Set db flag");
|
||||||
try {
|
try {
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
await metaStore
|
await metaStore
|
||||||
.put(const AppDbMetaEntryCompatV37(false).toEntry().toJson());
|
.put(const AppDbMetaEntryCompatV37(false).toEntry().toJson());
|
||||||
await transaction.completed;
|
await transaction.completed;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[setAppDbMigrationFlag] Failed while setting db flag, drop db instead",
|
"[setAppDbMigrationFlag] Failed while setting db flag, drop db instead",
|
||||||
|
@ -30,11 +31,13 @@ class CompatV37 {
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<bool> isAppDbNeedMigration(AppDb appDb) async {
|
static Future<bool> isAppDbNeedMigration(AppDb appDb) async {
|
||||||
final dbItem = await appDb.use((db) async {
|
final dbItem = await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadOnly),
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
(transaction) async {
|
||||||
return await metaStore.getObject(AppDbMetaEntryCompatV37.key) as Map?;
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
});
|
return await metaStore.getObject(AppDbMetaEntryCompatV37.key) as Map?;
|
||||||
|
},
|
||||||
|
);
|
||||||
if (dbItem == null) {
|
if (dbItem == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -51,51 +54,53 @@ class CompatV37 {
|
||||||
static Future<void> migrateAppDb(AppDb appDb) async {
|
static Future<void> migrateAppDb(AppDb appDb) async {
|
||||||
_log.info("[migrateAppDb] Migrate AppDb");
|
_log.info("[migrateAppDb] Migrate AppDb");
|
||||||
try {
|
try {
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.file2StoreName, AppDb.dirStoreName, AppDb.metaStoreName],
|
[AppDb.file2StoreName, AppDb.dirStoreName, AppDb.metaStoreName],
|
||||||
idbModeReadWrite);
|
idbModeReadWrite),
|
||||||
final noMediaFiles = <_NoMediaFile>[];
|
(transaction) async {
|
||||||
try {
|
final noMediaFiles = <_NoMediaFile>[];
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
try {
|
||||||
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
// scan the db to see which dirs contain a no media marker
|
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
||||||
await for (final c in fileStore.openCursor()) {
|
// scan the db to see which dirs contain a no media marker
|
||||||
final item = c.value as Map;
|
await for (final c in fileStore.openCursor()) {
|
||||||
final strippedPath = item["strippedPath"] as String;
|
final item = c.value as Map;
|
||||||
if (file_util.isNoMediaMarkerPath(strippedPath)) {
|
final strippedPath = item["strippedPath"] as String;
|
||||||
noMediaFiles.add(_NoMediaFile(
|
if (file_util.isNoMediaMarkerPath(strippedPath)) {
|
||||||
item["server"],
|
noMediaFiles.add(_NoMediaFile(
|
||||||
item["userId"],
|
item["server"],
|
||||||
path_lib
|
item["userId"],
|
||||||
.dirname(item["strippedPath"])
|
path_lib
|
||||||
.run((p) => p == "." ? "" : p),
|
.dirname(item["strippedPath"])
|
||||||
item["file"]["fileId"],
|
.run((p) => p == "." ? "" : p),
|
||||||
));
|
item["file"]["fileId"],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
c.next();
|
||||||
}
|
}
|
||||||
c.next();
|
// sort to make sure parent dirs are always in front of sub dirs
|
||||||
}
|
noMediaFiles
|
||||||
// sort to make sure parent dirs are always in front of sub dirs
|
.sort((a, b) => a.strippedDirPath.compareTo(b.strippedDirPath));
|
||||||
noMediaFiles
|
_log.info(
|
||||||
.sort((a, b) => a.strippedDirPath.compareTo(b.strippedDirPath));
|
"[migrateAppDb] nomedia dirs: ${noMediaFiles.toReadableString()}");
|
||||||
_log.info(
|
|
||||||
"[migrateAppDb] nomedia dirs: ${noMediaFiles.toReadableString()}");
|
|
||||||
|
|
||||||
if (noMediaFiles.isNotEmpty) {
|
if (noMediaFiles.isNotEmpty) {
|
||||||
await _migrateAppDbFileStore(appDb, noMediaFiles,
|
await _migrateAppDbFileStore(appDb, noMediaFiles,
|
||||||
fileStore: fileStore);
|
fileStore: fileStore);
|
||||||
await _migrateAppDbDirStore(appDb, noMediaFiles,
|
await _migrateAppDbDirStore(appDb, noMediaFiles,
|
||||||
dirStore: dirStore);
|
dirStore: dirStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
await metaStore
|
await metaStore
|
||||||
.put(const AppDbMetaEntryCompatV37(true).toEntry().toJson());
|
.put(const AppDbMetaEntryCompatV37(true).toEntry().toJson());
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
transaction.abort();
|
transaction.abort();
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[migrateAppDb] Failed while migrating, drop db instead", e,
|
_log.shout("[migrateAppDb] Failed while migrating, drop db instead", e,
|
||||||
stackTrace);
|
stackTrace);
|
||||||
|
|
|
@ -7,11 +7,13 @@ import 'package:nc_photos/object_extension.dart';
|
||||||
|
|
||||||
class DbCompatV5 {
|
class DbCompatV5 {
|
||||||
static Future<bool> isNeedMigration(AppDb appDb) async {
|
static Future<bool> isNeedMigration(AppDb appDb) async {
|
||||||
final dbItem = await appDb.use((db) async {
|
final dbItem = await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadOnly),
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
(transaction) async {
|
||||||
return await metaStore.getObject(AppDbMetaEntryDbCompatV5.key) as Map?;
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
});
|
return await metaStore.getObject(AppDbMetaEntryDbCompatV5.key) as Map?;
|
||||||
|
},
|
||||||
|
);
|
||||||
if (dbItem == null) {
|
if (dbItem == null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -28,36 +30,38 @@ class DbCompatV5 {
|
||||||
static Future<void> migrate(AppDb appDb) async {
|
static Future<void> migrate(AppDb appDb) async {
|
||||||
_log.info("[migrate] Migrate AppDb");
|
_log.info("[migrate] Migrate AppDb");
|
||||||
try {
|
try {
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.file2StoreName, AppDb.metaStoreName], idbModeReadWrite);
|
[AppDb.file2StoreName, AppDb.metaStoreName], idbModeReadWrite),
|
||||||
try {
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
try {
|
||||||
await for (final c in fileStore.openCursor()) {
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
final item = c.value as Map;
|
await for (final c in fileStore.openCursor()) {
|
||||||
// migrate file entry: add bestDateTime
|
final item = c.value as Map;
|
||||||
final fileEntry = item.cast<String, dynamic>().run((json) {
|
// migrate file entry: add bestDateTime
|
||||||
final f = File.fromJson(json["file"].cast<String, dynamic>());
|
final fileEntry = item.cast<String, dynamic>().run((json) {
|
||||||
return AppDbFile2Entry(
|
final f = File.fromJson(json["file"].cast<String, dynamic>());
|
||||||
json["server"],
|
return AppDbFile2Entry(
|
||||||
(json["userId"] as String).toCi(),
|
json["server"],
|
||||||
json["strippedPath"],
|
(json["userId"] as String).toCi(),
|
||||||
f.bestDateTime.millisecondsSinceEpoch,
|
json["strippedPath"],
|
||||||
File.fromJson(json["file"].cast<String, dynamic>()),
|
f.bestDateTime.millisecondsSinceEpoch,
|
||||||
);
|
File.fromJson(json["file"].cast<String, dynamic>()),
|
||||||
});
|
);
|
||||||
await c.update(fileEntry.toJson());
|
});
|
||||||
|
await c.update(fileEntry.toJson());
|
||||||
|
|
||||||
c.next();
|
c.next();
|
||||||
|
}
|
||||||
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
|
await metaStore
|
||||||
|
.put(const AppDbMetaEntryDbCompatV5(true).toEntry().toJson());
|
||||||
|
} catch (_) {
|
||||||
|
transaction.abort();
|
||||||
|
rethrow;
|
||||||
}
|
}
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
},
|
||||||
await metaStore
|
);
|
||||||
.put(const AppDbMetaEntryDbCompatV5(true).toEntry().toJson());
|
|
||||||
} catch (_) {
|
|
||||||
transaction.abort();
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
"[migrate] Failed while migrating, drop db instead", e, stackTrace);
|
"[migrate] Failed while migrating, drop db instead", e, stackTrace);
|
||||||
|
|
|
@ -19,12 +19,14 @@ class FindFile {
|
||||||
List<int> fileIds, {
|
List<int> fileIds, {
|
||||||
void Function(int fileId)? onFileNotFound,
|
void Function(int fileId)? onFileNotFound,
|
||||||
}) async {
|
}) async {
|
||||||
final dbItems = await _c.appDb.use((db) async {
|
final dbItems = await _c.appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
return await Future.wait(fileIds.map((id) =>
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
fileStore.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
|
return await Future.wait(fileIds.map((id) =>
|
||||||
});
|
fileStore.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
|
||||||
|
},
|
||||||
|
);
|
||||||
final files = <File>[];
|
final files = <File>[];
|
||||||
for (final pair in zip([fileIds, dbItems])) {
|
for (final pair in zip([fileIds, dbItems])) {
|
||||||
final dbItem = pair[1] as Map?;
|
final dbItem = pair[1] as Map?;
|
||||||
|
|
|
@ -18,24 +18,26 @@ class ListFavoriteOffline {
|
||||||
final rootDirs = account.roots
|
final rootDirs = account.roots
|
||||||
.map((r) => File(path: file_util.unstripPath(account, r)))
|
.map((r) => File(path: file_util.unstripPath(account, r)))
|
||||||
.toList();
|
.toList();
|
||||||
return _c.appDb.use((db) async {
|
return _c.appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
final fileIsFavoriteIndex =
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
fileStore.index(AppDbFile2Entry.fileIsFavoriteIndexName);
|
final fileIsFavoriteIndex =
|
||||||
return await fileIsFavoriteIndex
|
fileStore.index(AppDbFile2Entry.fileIsFavoriteIndexName);
|
||||||
.openCursor(
|
return await fileIsFavoriteIndex
|
||||||
key: AppDbFile2Entry.toFileIsFavoriteIndexKey(account, true),
|
.openCursor(
|
||||||
autoAdvance: true,
|
key: AppDbFile2Entry.toFileIsFavoriteIndexKey(account, true),
|
||||||
)
|
autoAdvance: true,
|
||||||
.map((c) => AppDbFile2Entry.fromJson(
|
)
|
||||||
(c.value as Map).cast<String, dynamic>()))
|
.map((c) => AppDbFile2Entry.fromJson(
|
||||||
.map((e) => e.file)
|
(c.value as Map).cast<String, dynamic>()))
|
||||||
.where((f) =>
|
.map((e) => e.file)
|
||||||
file_util.isSupportedFormat(f) &&
|
.where((f) =>
|
||||||
rootDirs.any((r) => file_util.isOrUnderDir(f, r)))
|
file_util.isSupportedFormat(f) &&
|
||||||
.toList();
|
rootDirs.any((r) => file_util.isOrUnderDir(f, r)))
|
||||||
});
|
.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
|
@ -11,20 +11,22 @@ class PopulatePerson {
|
||||||
|
|
||||||
/// Return a list of files of the faces
|
/// Return a list of files of the faces
|
||||||
Future<List<File>> call(Account account, List<Face> faces) async {
|
Future<List<File>> call(Account account, List<Face> faces) async {
|
||||||
return await appDb.use((db) async {
|
return await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
final products = <File>[];
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
for (final f in faces) {
|
final products = <File>[];
|
||||||
try {
|
for (final f in faces) {
|
||||||
products.add(await _populateOne(account, f, fileStore: store));
|
try {
|
||||||
} catch (e, stackTrace) {
|
products.add(await _populateOne(account, f, fileStore: store));
|
||||||
_log.severe("[call] Failed populating file of face: ${f.fileId}", e,
|
} catch (e, stackTrace) {
|
||||||
stackTrace);
|
_log.severe("[call] Failed populating file of face: ${f.fileId}", e,
|
||||||
|
stackTrace);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
return products;
|
||||||
return products;
|
},
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File> _populateOne(
|
Future<File> _populateOne(
|
||||||
|
|
|
@ -20,17 +20,19 @@ class ResyncAlbum {
|
||||||
final items = AlbumStaticProvider.of(album).items;
|
final items = AlbumStaticProvider.of(album).items;
|
||||||
final fileIds =
|
final fileIds =
|
||||||
items.whereType<AlbumFileItem>().map((i) => i.file.fileId!).toList();
|
items.whereType<AlbumFileItem>().map((i) => i.file.fileId!).toList();
|
||||||
final dbItems = Map.fromEntries(await appDb.use((db) async {
|
final dbItems = Map.fromEntries(await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
return await Future.wait(fileIds.map(
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
(id) async => MapEntry(
|
return await Future.wait(fileIds.map(
|
||||||
id,
|
(id) async => MapEntry(
|
||||||
await store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))
|
id,
|
||||||
as Map?,
|
await store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))
|
||||||
),
|
as Map?,
|
||||||
));
|
),
|
||||||
}));
|
));
|
||||||
|
},
|
||||||
|
));
|
||||||
return items.map((i) {
|
return items.map((i) {
|
||||||
if (i is AlbumFileItem) {
|
if (i is AlbumFileItem) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -12,22 +12,25 @@ class ScanDirOffline {
|
||||||
|
|
||||||
/// List all files under a dir recursively from the local DB
|
/// List all files under a dir recursively from the local DB
|
||||||
Future<List<File>> call(Account account, File root) async {
|
Future<List<File>> call(Account account, File root) async {
|
||||||
return await _c.appDb.use((db) async {
|
return await _c.appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadOnly);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
final index = store.index(AppDbFile2Entry.strippedPathIndexName);
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
final range = KeyRange.bound(
|
final index = store.index(AppDbFile2Entry.strippedPathIndexName);
|
||||||
AppDbFile2Entry.toStrippedPathIndexLowerKeyForDir(account, root),
|
final range = KeyRange.bound(
|
||||||
AppDbFile2Entry.toStrippedPathIndexUpperKeyForDir(account, root),
|
AppDbFile2Entry.toStrippedPathIndexLowerKeyForDir(account, root),
|
||||||
);
|
AppDbFile2Entry.toStrippedPathIndexUpperKeyForDir(account, root),
|
||||||
return await index
|
);
|
||||||
.openCursor(range: range, autoAdvance: true)
|
return await index
|
||||||
.map((c) => c.value)
|
.openCursor(range: range, autoAdvance: true)
|
||||||
.cast<Map>()
|
.map((c) => c.value)
|
||||||
.map((e) => AppDbFile2Entry.fromJson(e.cast<String, dynamic>()).file)
|
.cast<Map>()
|
||||||
.where((f) => file_util.isSupportedFormat(f))
|
.map(
|
||||||
.toList();
|
(e) => AppDbFile2Entry.fromJson(e.cast<String, dynamic>()).file)
|
||||||
});
|
.where((f) => file_util.isSupportedFormat(f))
|
||||||
|
.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
|
@ -100,7 +100,8 @@ class MockAppDb implements AppDb {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@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(
|
final db = await _dbFactory.open(
|
||||||
"test.db",
|
"test.db",
|
||||||
version: 1,
|
version: 1,
|
||||||
|
@ -110,9 +111,14 @@ class MockAppDb implements AppDb {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Transaction? transaction;
|
||||||
try {
|
try {
|
||||||
return await fn(db);
|
transaction = transactionBuilder(db);
|
||||||
|
return await fn(transaction);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (transaction != null) {
|
||||||
|
await transaction.completed;
|
||||||
|
}
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -341,36 +341,43 @@ Sharee buildSharee({
|
||||||
|
|
||||||
Future<void> fillAppDb(
|
Future<void> fillAppDb(
|
||||||
AppDb appDb, Account account, Iterable<File> files) async {
|
AppDb appDb, Account account, Iterable<File> files) async {
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.file2StoreName, idbModeReadWrite);
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
|
||||||
final file2Store = transaction.objectStore(AppDb.file2StoreName);
|
(transaction) async {
|
||||||
for (final f in files) {
|
final file2Store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
await file2Store.put(AppDbFile2Entry.fromFile(account, f).toJson(),
|
for (final f in files) {
|
||||||
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
|
await file2Store.put(AppDbFile2Entry.fromFile(account, f).toJson(),
|
||||||
}
|
AppDbFile2Entry.toPrimaryKeyForFile(account, f));
|
||||||
});
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> fillAppDbDir(
|
Future<void> fillAppDbDir(
|
||||||
AppDb appDb, Account account, File dir, List<File> children) async {
|
AppDb appDb, Account account, File dir, List<File> children) async {
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.dirStoreName, idbModeReadWrite);
|
(db) => db.transaction(AppDb.dirStoreName, idbModeReadWrite),
|
||||||
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
(transaction) async {
|
||||||
await dirStore.put(AppDbDirEntry.fromFiles(account, dir, children).toJson(),
|
final dirStore = transaction.objectStore(AppDb.dirStoreName);
|
||||||
AppDbDirEntry.toPrimaryKeyForDir(account, dir));
|
await dirStore.put(
|
||||||
});
|
AppDbDirEntry.fromFiles(account, dir, children).toJson(),
|
||||||
|
AppDbDirEntry.toPrimaryKeyForDir(account, dir));
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<T>> listAppDb<T>(
|
Future<List<T>> listAppDb<T>(
|
||||||
AppDb appDb, String storeName, T Function(JsonObj) transform) {
|
AppDb appDb, String storeName, T Function(JsonObj) transform) {
|
||||||
return appDb.use((db) async {
|
return appDb.use(
|
||||||
final transaction = db.transaction(storeName, idbModeReadOnly);
|
(db) => db.transaction(storeName, idbModeReadOnly),
|
||||||
final store = transaction.objectStore(storeName);
|
(transaction) async {
|
||||||
return await store
|
final store = transaction.objectStore(storeName);
|
||||||
.openCursor(autoAdvance: true)
|
return await store
|
||||||
.map((c) => c.value)
|
.openCursor(autoAdvance: true)
|
||||||
.cast<Map>()
|
.map((c) => c.value)
|
||||||
.map((e) => transform(e.cast<String, dynamic>()))
|
.cast<Map>()
|
||||||
.toList();
|
.map((e) => transform(e.cast<String, dynamic>()))
|
||||||
});
|
.toList();
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,12 +29,14 @@ void main() {
|
||||||
/// Expect: true
|
/// Expect: true
|
||||||
Future<void> _isAppDbNeedMigrationEntryFalse() async {
|
Future<void> _isAppDbNeedMigrationEntryFalse() async {
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
(transaction) async {
|
||||||
const entry = AppDbMetaEntryCompatV37(false);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
await metaStore.put(entry.toEntry().toJson());
|
const entry = AppDbMetaEntryCompatV37(false);
|
||||||
});
|
await metaStore.put(entry.toEntry().toJson());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await CompatV37.isAppDbNeedMigration(appDb), true);
|
expect(await CompatV37.isAppDbNeedMigration(appDb), true);
|
||||||
}
|
}
|
||||||
|
@ -44,12 +46,14 @@ Future<void> _isAppDbNeedMigrationEntryFalse() async {
|
||||||
/// Expect: false
|
/// Expect: false
|
||||||
Future<void> _isAppDbNeedMigrationEntryTrue() async {
|
Future<void> _isAppDbNeedMigrationEntryTrue() async {
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
(transaction) async {
|
||||||
const entry = AppDbMetaEntryCompatV37(true);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
await metaStore.put(entry.toEntry().toJson());
|
const entry = AppDbMetaEntryCompatV37(true);
|
||||||
});
|
await metaStore.put(entry.toEntry().toJson());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await CompatV37.isAppDbNeedMigration(appDb), false);
|
expect(await CompatV37.isAppDbNeedMigration(appDb), false);
|
||||||
}
|
}
|
||||||
|
@ -59,12 +63,14 @@ Future<void> _isAppDbNeedMigrationEntryTrue() async {
|
||||||
/// Expect: false
|
/// Expect: false
|
||||||
Future<void> _isAppDbNeedMigrationWithoutEntry() async {
|
Future<void> _isAppDbNeedMigrationWithoutEntry() async {
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction = db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
(transaction) async {
|
||||||
const entry = AppDbMetaEntryCompatV37(true);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
await metaStore.put(entry.toEntry().toJson());
|
const entry = AppDbMetaEntryCompatV37(true);
|
||||||
});
|
await metaStore.put(entry.toEntry().toJson());
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await CompatV37.isAppDbNeedMigration(appDb), false);
|
expect(await CompatV37.isAppDbNeedMigration(appDb), false);
|
||||||
}
|
}
|
||||||
|
@ -81,11 +87,9 @@ Future<void> _migrateAppDbWithoutNomedia() async {
|
||||||
..addJpeg("admin/dir1/test2.jpg"))
|
..addJpeg("admin/dir1/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await util.fillAppDb(appDb, account, files);
|
||||||
await util.fillAppDb(appDb, account, files);
|
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
|
||||||
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
|
await util.fillAppDbDir(appDb, account, files[2], [files[3]]);
|
||||||
await util.fillAppDbDir(appDb, account, files[2], [files[3]]);
|
|
||||||
});
|
|
||||||
await CompatV37.migrateAppDb(appDb);
|
await CompatV37.migrateAppDb(appDb);
|
||||||
|
|
||||||
final fileObjs = await util.listAppDb(
|
final fileObjs = await util.listAppDb(
|
||||||
|
@ -113,11 +117,9 @@ Future<void> _migrateAppDb() async {
|
||||||
..addJpeg("admin/dir1/test2.jpg"))
|
..addJpeg("admin/dir1/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await util.fillAppDb(appDb, account, files);
|
||||||
await util.fillAppDb(appDb, account, files);
|
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
|
||||||
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
|
await util.fillAppDbDir(appDb, account, files[2], files.slice(3, 5));
|
||||||
await util.fillAppDbDir(appDb, account, files[2], files.slice(3, 5));
|
|
||||||
});
|
|
||||||
await CompatV37.migrateAppDb(appDb);
|
await CompatV37.migrateAppDb(appDb);
|
||||||
|
|
||||||
final fileObjs = await util.listAppDb(
|
final fileObjs = await util.listAppDb(
|
||||||
|
@ -147,12 +149,10 @@ Future<void> _migrateAppDbNestedDir() async {
|
||||||
..addJpeg("admin/dir1/dir1-1/test3.jpg"))
|
..addJpeg("admin/dir1/dir1-1/test3.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await util.fillAppDb(appDb, account, files);
|
||||||
await util.fillAppDb(appDb, account, files);
|
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
|
||||||
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[2], files.slice(3, 6));
|
await util.fillAppDbDir(appDb, account, files[5], [files[6]]);
|
||||||
await util.fillAppDbDir(appDb, account, files[5], [files[6]]);
|
|
||||||
});
|
|
||||||
await CompatV37.migrateAppDb(appDb);
|
await CompatV37.migrateAppDb(appDb);
|
||||||
|
|
||||||
final fileObjs = await util.listAppDb(
|
final fileObjs = await util.listAppDb(
|
||||||
|
@ -183,12 +183,10 @@ Future<void> _migrateAppDbNestedMarker() async {
|
||||||
..addJpeg("admin/dir1/dir1-1/test3.jpg"))
|
..addJpeg("admin/dir1/dir1-1/test3.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await util.fillAppDb(appDb, account, files);
|
||||||
await util.fillAppDb(appDb, account, files);
|
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 3));
|
||||||
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[2], files.slice(3, 6));
|
await util.fillAppDbDir(appDb, account, files[5], files.slice(6, 8));
|
||||||
await util.fillAppDbDir(appDb, account, files[5], files.slice(6, 8));
|
|
||||||
});
|
|
||||||
await CompatV37.migrateAppDb(appDb);
|
await CompatV37.migrateAppDb(appDb);
|
||||||
|
|
||||||
final fileObjs = await util.listAppDb(
|
final fileObjs = await util.listAppDb(
|
||||||
|
@ -216,11 +214,9 @@ Future<void> _migrateAppDbRoot() async {
|
||||||
..addJpeg("admin/dir1/test2.jpg"))
|
..addJpeg("admin/dir1/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await util.fillAppDb(appDb, account, files);
|
||||||
await util.fillAppDb(appDb, account, files);
|
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 4));
|
||||||
await util.fillAppDbDir(appDb, account, files[0], files.slice(1, 4));
|
await util.fillAppDbDir(appDb, account, files[3], [files[4]]);
|
||||||
await util.fillAppDbDir(appDb, account, files[3], [files[4]]);
|
|
||||||
});
|
|
||||||
await CompatV37.migrateAppDb(appDb);
|
await CompatV37.migrateAppDb(appDb);
|
||||||
|
|
||||||
final objs = await util.listAppDb(
|
final objs = await util.listAppDb(
|
||||||
|
|
|
@ -13,39 +13,42 @@ void main() {
|
||||||
group("isNeedMigration", () {
|
group("isNeedMigration", () {
|
||||||
test("w/ meta entry == false", () async {
|
test("w/ meta entry == false", () async {
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
const entry = AppDbMetaEntryDbCompatV5(false);
|
const entry = AppDbMetaEntryDbCompatV5(false);
|
||||||
await metaStore.put(entry.toEntry().toJson());
|
await metaStore.put(entry.toEntry().toJson());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await DbCompatV5.isNeedMigration(appDb), true);
|
expect(await DbCompatV5.isNeedMigration(appDb), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("w/ meta entry == true", () async {
|
test("w/ meta entry == true", () async {
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
const entry = AppDbMetaEntryDbCompatV5(true);
|
const entry = AppDbMetaEntryDbCompatV5(true);
|
||||||
await metaStore.put(entry.toEntry().toJson());
|
await metaStore.put(entry.toEntry().toJson());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await DbCompatV5.isNeedMigration(appDb), false);
|
expect(await DbCompatV5.isNeedMigration(appDb), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("w/o meta entry", () async {
|
test("w/o meta entry", () async {
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.metaStoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.metaStoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
final metaStore = transaction.objectStore(AppDb.metaStoreName);
|
||||||
const entry = AppDbMetaEntryDbCompatV5(true);
|
const entry = AppDbMetaEntryDbCompatV5(true);
|
||||||
await metaStore.put(entry.toEntry().toJson());
|
await metaStore.put(entry.toEntry().toJson());
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
expect(await DbCompatV5.isNeedMigration(appDb), false);
|
expect(await DbCompatV5.isNeedMigration(appDb), false);
|
||||||
});
|
});
|
||||||
|
@ -60,17 +63,18 @@ void main() {
|
||||||
))
|
))
|
||||||
.build();
|
.build();
|
||||||
final appDb = MockAppDb();
|
final appDb = MockAppDb();
|
||||||
await appDb.use((db) async {
|
await appDb.use(
|
||||||
final transaction =
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadWrite),
|
||||||
db.transaction(AppDb.file2StoreName, idbModeReadWrite);
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
await fileStore.put({
|
await fileStore.put({
|
||||||
"server": account.url,
|
"server": account.url,
|
||||||
"userId": account.username.toCaseInsensitiveString(),
|
"userId": account.username.toCaseInsensitiveString(),
|
||||||
"strippedPath": files[0].strippedPathWithEmpty,
|
"strippedPath": files[0].strippedPathWithEmpty,
|
||||||
"file": files[0].toJson(),
|
"file": files[0].toJson(),
|
||||||
}, "${account.url}/${account.username.toCaseInsensitiveString()}/${files[0].fileId}");
|
}, "${account.url}/${account.username.toCaseInsensitiveString()}/${files[0].fileId}");
|
||||||
});
|
},
|
||||||
|
);
|
||||||
await DbCompatV5.migrate(appDb);
|
await DbCompatV5.migrate(appDb);
|
||||||
|
|
||||||
final objs =
|
final objs =
|
||||||
|
|
Loading…
Reference in a new issue