mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-02-24 18:38:48 +01:00
Parse json in isolate
This commit is contained in:
parent
687cbbc4df
commit
763762d385
5 changed files with 115 additions and 73 deletions
|
@ -13,12 +13,12 @@ import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/webdav_response_parser.dart';
|
import 'package:nc_photos/entity/webdav_response_parser.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:nc_photos/or_null.dart';
|
import 'package:nc_photos/or_null.dart';
|
||||||
import 'package:nc_photos/touch_token_manager.dart';
|
import 'package:nc_photos/touch_token_manager.dart';
|
||||||
import 'package:nc_photos/use_case/compat/v32.dart';
|
import 'package:nc_photos/use_case/compat/v32.dart';
|
||||||
import 'package:path/path.dart' as path_lib;
|
import 'package:path/path.dart' as path_lib;
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
import 'package:xml/xml.dart';
|
import 'package:xml/xml.dart';
|
||||||
|
|
||||||
|
@ -269,9 +269,9 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
const FileAppDbDataSource(this.appDb);
|
const FileAppDbDataSource(this.appDb);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
list(Account account, File dir) {
|
list(Account account, File dir) async {
|
||||||
_log.info("[list] ${dir.path}");
|
_log.info("[list] ${dir.path}");
|
||||||
return appDb.use(
|
final dbItems = await appDb.use(
|
||||||
(db) => db.transaction(
|
(db) => db.transaction(
|
||||||
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadOnly),
|
[AppDb.dirStoreName, AppDb.file2StoreName], idbModeReadOnly),
|
||||||
(transaction) async {
|
(transaction) async {
|
||||||
|
@ -284,21 +284,31 @@ class FileAppDbDataSource implements FileDataSource {
|
||||||
}
|
}
|
||||||
final dirEntry =
|
final dirEntry =
|
||||||
AppDbDirEntry.fromJson(dirItem.cast<String, dynamic>());
|
AppDbDirEntry.fromJson(dirItem.cast<String, dynamic>());
|
||||||
final entries = await dirEntry.children.mapStream((c) async {
|
return Tuple2(
|
||||||
|
dirEntry.dir,
|
||||||
|
await Future.wait(
|
||||||
|
dirEntry.children.map((c) async {
|
||||||
final fileItem = await fileStore
|
final fileItem = await fileStore
|
||||||
.getObject(AppDbFile2Entry.toPrimaryKey(account, c)) as Map?;
|
.getObject(AppDbFile2Entry.toPrimaryKey(account, c)) as Map?;
|
||||||
if (fileItem == null) {
|
if (fileItem == null) {
|
||||||
_log.warning(
|
_log.warning(
|
||||||
"[list] Missing file ($c) in db for dir: ${logFilename(dir.path)}");
|
"[list] Missing file ($c) in db for dir: ${logFilename(dir.path)}");
|
||||||
throw CacheNotFoundException("No entry for dir child: $c");
|
throw CacheNotFoundException("No entry for dir child: $c");
|
||||||
|
} else {
|
||||||
|
return fileItem;
|
||||||
}
|
}
|
||||||
return AppDbFile2Entry.fromJson(fileItem.cast<String, dynamic>());
|
}),
|
||||||
}, k.simultaneousQuery).toList();
|
eagerError: true,
|
||||||
// we need to add dir to match the remote query
|
),
|
||||||
return [dirEntry.dir] +
|
);
|
||||||
entries.map((e) => e.file).where((f) => _validateFile(f)).toList();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
// we need to add dir to match the remote query
|
||||||
|
return [
|
||||||
|
dbItems.item1,
|
||||||
|
...(await dbItems.item2.computeAll(_covertAppDbFile2Entry))
|
||||||
|
.where((f) => _validateFile(f))
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -647,20 +657,16 @@ class FileForwardCacheManager {
|
||||||
|
|
||||||
// query other files
|
// query other files
|
||||||
if (needQuery.isNotEmpty) {
|
if (needQuery.isNotEmpty) {
|
||||||
final fileItems = await appDb.use(
|
final dbItems = await appDb.use(
|
||||||
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
(transaction) async {
|
(transaction) async {
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
return await needQuery
|
return await Future.wait(needQuery.map((id) =>
|
||||||
.mapStream(
|
store.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
|
||||||
(id) => store
|
|
||||||
.getObject(AppDbFile2Entry.toPrimaryKey(account, id)),
|
|
||||||
k.simultaneousQuery)
|
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
files.addAll(fileItems.cast<Map?>().whereType<Map>().map(
|
files.addAll(
|
||||||
(i) => AppDbFile2Entry.fromJson(i.cast<String, dynamic>()).file));
|
await dbItems.whereType<Map>().computeAll(_covertAppDbFile2Entry));
|
||||||
}
|
}
|
||||||
_fileCache.addEntries(files.map((f) => MapEntry(f.fileId!, f)));
|
_fileCache.addEntries(files.map((f) => MapEntry(f.fileId!, f)));
|
||||||
_log.info(
|
_log.info(
|
||||||
|
@ -692,3 +698,6 @@ bool _validateFile(File f) {
|
||||||
// See: https://gitlab.com/nkming2/nc-photos/-/issues/9
|
// See: https://gitlab.com/nkming2/nc-photos/-/issues/9
|
||||||
return f.lastModified != null;
|
return f.lastModified != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File _covertAppDbFile2Entry(Map json) =>
|
||||||
|
AppDbFile2Entry.fromJson(json.cast<String, dynamic>()).file;
|
||||||
|
|
|
@ -114,4 +114,34 @@ extension IterableExtension<T> on Iterable<T> {
|
||||||
yield e;
|
yield e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<U>> computeAll<U>(ComputeCallback<T, U> callback) async {
|
||||||
|
return await compute(
|
||||||
|
_computeAllImpl<T, U>, _ComputeAllMessage(callback, asList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a list containing elements in this iterable
|
||||||
|
///
|
||||||
|
/// If this Iterable is itself a list, this will be returned directly with no
|
||||||
|
/// copying
|
||||||
|
List<T> asList() {
|
||||||
|
if (this is List) {
|
||||||
|
return this as List<T>;
|
||||||
|
} else {
|
||||||
|
return toList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ComputeAllMessage<T, U> {
|
||||||
|
const _ComputeAllMessage(this.callback, this.data);
|
||||||
|
|
||||||
|
final ComputeCallback<T, U> callback;
|
||||||
|
final List<T> data;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<U>> _computeAllImpl<T, U>(_ComputeAllMessage<T, U> message) async {
|
||||||
|
final result = await Future.wait(
|
||||||
|
message.data.map((e) async => await message.callback(e)));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:idb_shim/idb_client.dart';
|
import 'package:idb_shim/idb_client.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_db.dart';
|
import 'package:nc_photos/app_db.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
|
||||||
import 'package:quiver/iterables.dart';
|
|
||||||
|
|
||||||
class FindFile {
|
class FindFile {
|
||||||
FindFile(this._c) : assert(require(_c));
|
FindFile(this._c) : assert(require(_c));
|
||||||
|
@ -25,27 +23,22 @@ class FindFile {
|
||||||
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
(transaction) async {
|
(transaction) async {
|
||||||
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
final fileStore = transaction.objectStore(AppDb.file2StoreName);
|
||||||
return await fileIds
|
return await Future.wait(fileIds.map((id) =>
|
||||||
.mapStream(
|
fileStore.getObject(AppDbFile2Entry.toPrimaryKey(account, id))));
|
||||||
(id) => fileStore
|
|
||||||
.getObject(AppDbFile2Entry.toPrimaryKey(account, id)),
|
|
||||||
k.simultaneousQuery)
|
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
final fileMap = await compute(_covertFileMap, dbItems);
|
||||||
final files = <File>[];
|
final files = <File>[];
|
||||||
for (final pair in zip([fileIds, dbItems])) {
|
for (final id in fileIds) {
|
||||||
final dbItem = pair[1] as Map?;
|
final f = fileMap[id];
|
||||||
if (dbItem == null) {
|
if (f == null) {
|
||||||
if (onFileNotFound == null) {
|
if (onFileNotFound == null) {
|
||||||
throw StateError("File ID not found: ${pair[0]}");
|
throw StateError("File ID not found: $id");
|
||||||
} else {
|
} else {
|
||||||
onFileNotFound(pair[0] as int);
|
onFileNotFound(id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final dbEntry =
|
files.add(f);
|
||||||
AppDbFile2Entry.fromJson(dbItem.cast<String, dynamic>());
|
|
||||||
files.add(dbEntry.file);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return files;
|
return files;
|
||||||
|
@ -53,3 +46,10 @@ class FindFile {
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<int, File> _covertFileMap(List<Object?> dbItems) {
|
||||||
|
return Map.fromEntries(dbItems
|
||||||
|
.whereType<Map>()
|
||||||
|
.map((j) => AppDbFile2Entry.fromJson(j.cast<String, dynamic>()).file)
|
||||||
|
.map((f) => MapEntry(f.fileId!, f)));
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:idb_shim/idb_client.dart';
|
import 'package:idb_shim/idb_client.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
@ -6,8 +7,7 @@ import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/item.dart';
|
import 'package:nc_photos/entity/album/item.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/iterable_extension.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
|
||||||
|
|
||||||
/// Resync files inside an album with the file db
|
/// Resync files inside an album with the file db
|
||||||
class ResyncAlbum {
|
class ResyncAlbum {
|
||||||
|
@ -20,31 +20,21 @@ class ResyncAlbum {
|
||||||
"Resync only make sense for static albums: ${album.name}");
|
"Resync only make sense for static albums: ${album.name}");
|
||||||
}
|
}
|
||||||
final items = AlbumStaticProvider.of(album).items;
|
final items = AlbumStaticProvider.of(album).items;
|
||||||
final fileIds =
|
final dbItems = await appDb.use(
|
||||||
items.whereType<AlbumFileItem>().map((i) => i.file.fileId!).toList();
|
|
||||||
final dbItems = Map.fromEntries(await appDb.use(
|
|
||||||
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
(transaction) async {
|
(transaction) async {
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
return await fileIds
|
return await Future.wait(items.whereType<AlbumFileItem>().map((i) =>
|
||||||
.mapStream(
|
store.getObject(
|
||||||
(id) async => MapEntry(
|
AppDbFile2Entry.toPrimaryKey(account, i.file.fileId!))));
|
||||||
id,
|
|
||||||
await store.getObject(
|
|
||||||
AppDbFile2Entry.toPrimaryKey(account, id)) as Map?,
|
|
||||||
),
|
|
||||||
k.simultaneousQuery)
|
|
||||||
.toList();
|
|
||||||
},
|
},
|
||||||
));
|
);
|
||||||
|
final fileMap = await compute(_covertFileMap, dbItems);
|
||||||
return items.map((i) {
|
return items.map((i) {
|
||||||
if (i is AlbumFileItem) {
|
if (i is AlbumFileItem) {
|
||||||
try {
|
try {
|
||||||
final dbItem = dbItems[i.file.fileId]!;
|
|
||||||
final dbEntry =
|
|
||||||
AppDbFile2Entry.fromJson(dbItem.cast<String, dynamic>());
|
|
||||||
return i.copyWith(
|
return i.copyWith(
|
||||||
file: dbEntry.file,
|
file: fileMap[i.file.fileId]!,
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout(
|
_log.shout(
|
||||||
|
@ -63,3 +53,10 @@ class ResyncAlbum {
|
||||||
|
|
||||||
static final _log = Logger("use_case.resync_album.ResyncAlbum");
|
static final _log = Logger("use_case.resync_album.ResyncAlbum");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<int, File> _covertFileMap(List<Object?> dbItems) {
|
||||||
|
return Map.fromEntries(dbItems
|
||||||
|
.whereType<Map>()
|
||||||
|
.map((j) => AppDbFile2Entry.fromJson(j.cast<String, dynamic>()).file)
|
||||||
|
.map((f) => MapEntry(f.fileId!, f)));
|
||||||
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import 'package:nc_photos/app_db.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
|
import 'package:nc_photos/iterable_extension.dart';
|
||||||
|
|
||||||
class ScanDirOffline {
|
class ScanDirOffline {
|
||||||
ScanDirOffline(this._c) : assert(require(_c));
|
ScanDirOffline(this._c) : assert(require(_c));
|
||||||
|
@ -11,12 +12,12 @@ class ScanDirOffline {
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.appDb);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.appDb);
|
||||||
|
|
||||||
/// 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(
|
Future<Iterable<File>> call(
|
||||||
Account account,
|
Account account,
|
||||||
File root, {
|
File root, {
|
||||||
bool isOnlySupportedFormat = true,
|
bool isOnlySupportedFormat = true,
|
||||||
}) async {
|
}) async {
|
||||||
return await _c.appDb.use(
|
final dbItems = await _c.appDb.use(
|
||||||
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
(db) => db.transaction(AppDb.file2StoreName, idbModeReadOnly),
|
||||||
(transaction) async {
|
(transaction) async {
|
||||||
final store = transaction.objectStore(AppDb.file2StoreName);
|
final store = transaction.objectStore(AppDb.file2StoreName);
|
||||||
|
@ -25,20 +26,25 @@ class ScanDirOffline {
|
||||||
AppDbFile2Entry.toStrippedPathIndexLowerKeyForDir(account, root),
|
AppDbFile2Entry.toStrippedPathIndexLowerKeyForDir(account, root),
|
||||||
AppDbFile2Entry.toStrippedPathIndexUpperKeyForDir(account, root),
|
AppDbFile2Entry.toStrippedPathIndexUpperKeyForDir(account, root),
|
||||||
);
|
);
|
||||||
final product = <File>[];
|
return await index
|
||||||
await for (final c
|
.openCursor(range: range, autoAdvance: false)
|
||||||
in index.openCursor(range: range, autoAdvance: false)) {
|
.map((c) {
|
||||||
final e = AppDbFile2Entry.fromJson(
|
final v = c.value as Map;
|
||||||
(c.value as Map).cast<String, dynamic>());
|
|
||||||
if (!isOnlySupportedFormat || file_util.isSupportedFormat(e.file)) {
|
|
||||||
product.add(e.file);
|
|
||||||
}
|
|
||||||
c.next();
|
c.next();
|
||||||
}
|
return v;
|
||||||
return product;
|
}).toList();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
final results = await dbItems.computeAll(_covertAppDbFile2Entry);
|
||||||
|
if (isOnlySupportedFormat) {
|
||||||
|
return results.where((f) => file_util.isSupportedFormat(f));
|
||||||
|
} else {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File _covertAppDbFile2Entry(Map json) =>
|
||||||
|
AppDbFile2Entry.fromJson(json.cast<String, dynamic>()).file;
|
||||||
|
|
Loading…
Reference in a new issue