mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-10 09:18:52 +01:00
Merge branch 'refactor-db' into dev
This commit is contained in:
commit
a4018cd3f3
134 changed files with 8146 additions and 4077 deletions
|
@ -1,4 +1,3 @@
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -30,19 +29,16 @@ import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/share/data_source.dart';
|
import 'package:nc_photos/entity/share/data_source.dart';
|
||||||
import 'package:nc_photos/entity/sharee.dart';
|
import 'package:nc_photos/entity/sharee.dart';
|
||||||
import 'package:nc_photos/entity/sharee/data_source.dart';
|
import 'package:nc_photos/entity/sharee/data_source.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/isolate_util.dart' as sql_isolate;
|
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
import 'package:nc_photos/entity/tag.dart';
|
||||||
import 'package:nc_photos/entity/tag/data_source.dart';
|
import 'package:nc_photos/entity/tag/data_source.dart';
|
||||||
import 'package:nc_photos/entity/tagged_file.dart';
|
import 'package:nc_photos/entity/tagged_file.dart';
|
||||||
import 'package:nc_photos/entity/tagged_file/data_source.dart';
|
import 'package:nc_photos/entity/tagged_file/data_source.dart';
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/android_info.dart';
|
import 'package:nc_photos/mobile/android/android_info.dart';
|
||||||
import 'package:nc_photos/mobile/platform.dart'
|
|
||||||
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
|
||||||
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
||||||
import 'package:nc_photos/platform/features.dart' as features;
|
import 'package:nc_photos/platform/features.dart' as features;
|
||||||
import 'package:nc_photos/touch_manager.dart';
|
import 'package:nc_photos/touch_manager.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_gps_map/np_gps_map.dart';
|
import 'package:np_gps_map/np_gps_map.dart';
|
||||||
import 'package:np_log/np_log.dart' as np_log;
|
import 'package:np_log/np_log.dart' as np_log;
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
@ -64,10 +60,6 @@ Future<void> init(InitIsolateType isolateType) async {
|
||||||
|
|
||||||
initLog();
|
initLog();
|
||||||
await _initDeviceInfo();
|
await _initDeviceInfo();
|
||||||
initDrift();
|
|
||||||
if (isolateType == InitIsolateType.main) {
|
|
||||||
await _initDriftWorkaround();
|
|
||||||
}
|
|
||||||
_initKiwi();
|
_initKiwi();
|
||||||
await _initPref();
|
await _initPref();
|
||||||
await _initAccountPrefs();
|
await _initAccountPrefs();
|
||||||
|
@ -93,19 +85,6 @@ void initLog() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void initDrift() {
|
|
||||||
driftRuntimeOptions.debugPrint = (log) => debugPrint(log, wrapWidth: 1024);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initDriftWorkaround() async {
|
|
||||||
if (getRawPlatform() == NpPlatform.android && AndroidInfo().sdkInt < 24) {
|
|
||||||
_log.info("[_initDriftWorkaround] Workaround Android 6- bug");
|
|
||||||
// see: https://github.com/flutter/flutter/issues/73318 and
|
|
||||||
// https://github.com/simolus3/drift/issues/895
|
|
||||||
await platform.applyWorkaroundToOpenSqlite3OnOldAndroidVersions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _initPref() async {
|
Future<void> _initPref() async {
|
||||||
final provider = PrefSharedPreferencesProvider();
|
final provider = PrefSharedPreferencesProvider();
|
||||||
await provider.init();
|
await provider.init();
|
||||||
|
@ -156,15 +135,15 @@ void _initSelfSignedCertManager() {
|
||||||
Future<void> _initDiContainer(InitIsolateType isolateType) async {
|
Future<void> _initDiContainer(InitIsolateType isolateType) async {
|
||||||
final c = DiContainer.late();
|
final c = DiContainer.late();
|
||||||
c.pref = Pref();
|
c.pref = Pref();
|
||||||
c.sqliteDb = await _createDb(isolateType);
|
c.npDb = await _createDb(isolateType);
|
||||||
|
|
||||||
c.albumRepo = AlbumRepo(AlbumCachedDataSource(c));
|
c.albumRepo = AlbumRepo(AlbumCachedDataSource(c));
|
||||||
c.albumRepoRemote = AlbumRepo(AlbumRemoteDataSource());
|
c.albumRepoRemote = AlbumRepo(AlbumRemoteDataSource());
|
||||||
c.albumRepoLocal = AlbumRepo(AlbumSqliteDbDataSource(c));
|
c.albumRepoLocal = AlbumRepo(AlbumSqliteDbDataSource(c));
|
||||||
c.albumRepo2 = CachedAlbumRepo2(
|
c.albumRepo2 = CachedAlbumRepo2(
|
||||||
const AlbumRemoteDataSource2(), AlbumSqliteDbDataSource2(c.sqliteDb));
|
const AlbumRemoteDataSource2(), AlbumSqliteDbDataSource2(c.npDb));
|
||||||
c.albumRepo2Remote = const BasicAlbumRepo2(AlbumRemoteDataSource2());
|
c.albumRepo2Remote = const BasicAlbumRepo2(AlbumRemoteDataSource2());
|
||||||
c.albumRepo2Local = BasicAlbumRepo2(AlbumSqliteDbDataSource2(c.sqliteDb));
|
c.albumRepo2Local = BasicAlbumRepo2(AlbumSqliteDbDataSource2(c.npDb));
|
||||||
c.fileRepo = FileRepo(FileCachedDataSource(c));
|
c.fileRepo = FileRepo(FileCachedDataSource(c));
|
||||||
c.fileRepoRemote = const FileRepo(FileWebdavDataSource());
|
c.fileRepoRemote = const FileRepo(FileWebdavDataSource());
|
||||||
c.fileRepoLocal = FileRepo(FileSqliteDbDataSource(c));
|
c.fileRepoLocal = FileRepo(FileSqliteDbDataSource(c));
|
||||||
|
@ -173,25 +152,25 @@ Future<void> _initDiContainer(InitIsolateType isolateType) async {
|
||||||
c.favoriteRepo = const FavoriteRepo(FavoriteRemoteDataSource());
|
c.favoriteRepo = const FavoriteRepo(FavoriteRemoteDataSource());
|
||||||
c.tagRepo = const TagRepo(TagRemoteDataSource());
|
c.tagRepo = const TagRepo(TagRemoteDataSource());
|
||||||
c.tagRepoRemote = const TagRepo(TagRemoteDataSource());
|
c.tagRepoRemote = const TagRepo(TagRemoteDataSource());
|
||||||
c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.npDb));
|
||||||
c.taggedFileRepo = const TaggedFileRepo(TaggedFileRemoteDataSource());
|
c.taggedFileRepo = const TaggedFileRepo(TaggedFileRemoteDataSource());
|
||||||
c.searchRepo = SearchRepo(SearchSqliteDbDataSource(c));
|
c.searchRepo = SearchRepo(SearchSqliteDbDataSource(c));
|
||||||
c.ncAlbumRepo = CachedNcAlbumRepo(
|
c.ncAlbumRepo = CachedNcAlbumRepo(
|
||||||
const NcAlbumRemoteDataSource(), NcAlbumSqliteDbDataSource(c.sqliteDb));
|
const NcAlbumRemoteDataSource(), NcAlbumSqliteDbDataSource(c.npDb));
|
||||||
c.ncAlbumRepoRemote = const BasicNcAlbumRepo(NcAlbumRemoteDataSource());
|
c.ncAlbumRepoRemote = const BasicNcAlbumRepo(NcAlbumRemoteDataSource());
|
||||||
c.ncAlbumRepoLocal = BasicNcAlbumRepo(NcAlbumSqliteDbDataSource(c.sqliteDb));
|
c.ncAlbumRepoLocal = BasicNcAlbumRepo(NcAlbumSqliteDbDataSource(c.npDb));
|
||||||
c.faceRecognitionPersonRepo = const BasicFaceRecognitionPersonRepo(
|
c.faceRecognitionPersonRepo = const BasicFaceRecognitionPersonRepo(
|
||||||
FaceRecognitionPersonRemoteDataSource());
|
FaceRecognitionPersonRemoteDataSource());
|
||||||
c.faceRecognitionPersonRepoRemote = const BasicFaceRecognitionPersonRepo(
|
c.faceRecognitionPersonRepoRemote = const BasicFaceRecognitionPersonRepo(
|
||||||
FaceRecognitionPersonRemoteDataSource());
|
FaceRecognitionPersonRemoteDataSource());
|
||||||
c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
||||||
FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
FaceRecognitionPersonSqliteDbDataSource(c.npDb));
|
||||||
c.recognizeFaceRepo =
|
c.recognizeFaceRepo =
|
||||||
const BasicRecognizeFaceRepo(RecognizeFaceRemoteDataSource());
|
const BasicRecognizeFaceRepo(RecognizeFaceRemoteDataSource());
|
||||||
c.recognizeFaceRepoRemote =
|
c.recognizeFaceRepoRemote =
|
||||||
const BasicRecognizeFaceRepo(RecognizeFaceRemoteDataSource());
|
const BasicRecognizeFaceRepo(RecognizeFaceRemoteDataSource());
|
||||||
c.recognizeFaceRepoLocal =
|
c.recognizeFaceRepoLocal =
|
||||||
BasicRecognizeFaceRepo(RecognizeFaceSqliteDbDataSource(c.sqliteDb));
|
BasicRecognizeFaceRepo(RecognizeFaceSqliteDbDataSource(c.npDb));
|
||||||
|
|
||||||
c.touchManager = TouchManager(c);
|
c.touchManager = TouchManager(c);
|
||||||
|
|
||||||
|
@ -207,21 +186,16 @@ void _initVisibilityDetector() {
|
||||||
VisibilityDetectorController.instance.updateInterval = Duration.zero;
|
VisibilityDetectorController.instance.updateInterval = Duration.zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<sql.SqliteDb> _createDb(InitIsolateType isolateType) async {
|
Future<NpDb> _createDb(InitIsolateType isolateType) async {
|
||||||
switch (isolateType) {
|
final npDb = NpDb();
|
||||||
case InitIsolateType.main:
|
final androidSdk =
|
||||||
// use driftIsolate to prevent DB blocking the UI thread
|
getRawPlatform() == NpPlatform.android ? AndroidInfo().sdkInt : null;
|
||||||
if (getRawPlatform() == NpPlatform.web) {
|
if (isolateType == InitIsolateType.main) {
|
||||||
// no isolate support on web
|
await npDb.initMainIsolate(androidSdk: androidSdk);
|
||||||
return sql.SqliteDb();
|
} else {
|
||||||
} else {
|
await npDb.initBackgroundIsolate(androidSdk: androidSdk);
|
||||||
return sql_isolate.createDb();
|
|
||||||
}
|
|
||||||
|
|
||||||
case InitIsolateType.flutterIsolate:
|
|
||||||
// service already runs in an isolate
|
|
||||||
return sql.SqliteDb();
|
|
||||||
}
|
}
|
||||||
|
return npDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
final _log = Logger("app_init");
|
final _log = Logger("app_init");
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:nc_photos/entity/file_descriptor.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/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/throttler.dart';
|
import 'package:nc_photos/throttler.dart';
|
||||||
import 'package:nc_photos/use_case/list_location_file.dart';
|
|
||||||
import 'package:nc_photos/use_case/list_location_group.dart';
|
import 'package:nc_photos/use_case/list_location_group.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
@ -90,7 +89,6 @@ class ListLocationBloc
|
||||||
extends Bloc<ListLocationBlocEvent, ListLocationBlocState> {
|
extends Bloc<ListLocationBlocEvent, ListLocationBlocState> {
|
||||||
ListLocationBloc(this._c)
|
ListLocationBloc(this._c)
|
||||||
: assert(require(_c)),
|
: assert(require(_c)),
|
||||||
assert(ListLocationFile.require(_c)),
|
|
||||||
super(ListLocationBlocInit()) {
|
super(ListLocationBlocInit()) {
|
||||||
_fileRemovedEventListener.begin();
|
_fileRemovedEventListener.begin();
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,6 @@ class ScanAccountDirBloc
|
||||||
ScanAccountDirBloc._(this.account) : super(const ScanAccountDirBlocInit()) {
|
ScanAccountDirBloc._(this.account) : super(const ScanAccountDirBlocInit()) {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
assert(require(c));
|
assert(require(c));
|
||||||
assert(ScanDirOffline.require(c));
|
|
||||||
_c = c;
|
_c = c;
|
||||||
|
|
||||||
_fileRemovedEventListener.begin();
|
_fileRemovedEventListener.begin();
|
||||||
|
@ -437,10 +436,13 @@ class ScanAccountDirBloc
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query a small amount of files to give an illusion of quick startup
|
/// Query a small amount of files to give an illusion of quick startup
|
||||||
Future<List<File>> _queryOfflineMini(ScanAccountDirBlocQueryBase ev) async {
|
Future<List<FileDescriptor>> _queryOfflineMini(
|
||||||
|
ScanAccountDirBlocQueryBase ev) async {
|
||||||
return await ScanDirOfflineMini(_c)(
|
return await ScanDirOfflineMini(_c)(
|
||||||
account,
|
account,
|
||||||
account.roots.map((r) => File(path: file_util.unstripPath(account, r))),
|
account.roots
|
||||||
|
.map((r) => File(path: file_util.unstripPath(account, r)))
|
||||||
|
.toList(),
|
||||||
scanMiniCount,
|
scanMiniCount,
|
||||||
isOnlySupportedFormat: true,
|
isOnlySupportedFormat: true,
|
||||||
);
|
);
|
||||||
|
|
|
@ -58,7 +58,7 @@ abstract class SearchBlocState {
|
||||||
|
|
||||||
final Account? account;
|
final Account? account;
|
||||||
final SearchCriteria criteria;
|
final SearchCriteria criteria;
|
||||||
final List<File> items;
|
final List<FileDescriptor> items;
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchBlocInit extends SearchBlocState {
|
class SearchBlocInit extends SearchBlocState {
|
||||||
|
@ -67,20 +67,20 @@ class SearchBlocInit extends SearchBlocState {
|
||||||
|
|
||||||
class SearchBlocLoading extends SearchBlocState {
|
class SearchBlocLoading extends SearchBlocState {
|
||||||
const SearchBlocLoading(
|
const SearchBlocLoading(
|
||||||
Account? account, SearchCriteria criteria, List<File> items)
|
Account? account, SearchCriteria criteria, List<FileDescriptor> items)
|
||||||
: super(account, criteria, items);
|
: super(account, criteria, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchBlocSuccess extends SearchBlocState {
|
class SearchBlocSuccess extends SearchBlocState {
|
||||||
const SearchBlocSuccess(
|
const SearchBlocSuccess(
|
||||||
Account? account, SearchCriteria criteria, List<File> items)
|
Account? account, SearchCriteria criteria, List<FileDescriptor> items)
|
||||||
: super(account, criteria, items);
|
: super(account, criteria, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class SearchBlocFailure extends SearchBlocState {
|
class SearchBlocFailure extends SearchBlocState {
|
||||||
const SearchBlocFailure(Account? account, SearchCriteria criteria,
|
const SearchBlocFailure(Account? account, SearchCriteria criteria,
|
||||||
List<File> items, this.exception)
|
List<FileDescriptor> items, this.exception)
|
||||||
: super(account, criteria, items);
|
: super(account, criteria, items);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -93,7 +93,7 @@ class SearchBlocFailure extends SearchBlocState {
|
||||||
/// may have been changed externally
|
/// may have been changed externally
|
||||||
class SearchBlocInconsistent extends SearchBlocState {
|
class SearchBlocInconsistent extends SearchBlocState {
|
||||||
const SearchBlocInconsistent(
|
const SearchBlocInconsistent(
|
||||||
Account? account, SearchCriteria criteria, List<File> items)
|
Account? account, SearchCriteria criteria, List<FileDescriptor> items)
|
||||||
: super(account, criteria, items);
|
: super(account, criteria, items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +196,7 @@ class SearchBloc extends Bloc<SearchBlocEvent, SearchBlocState> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<File>> _query(SearchBlocQuery ev) =>
|
Future<List<FileDescriptor>> _query(SearchBlocQuery ev) =>
|
||||||
Search(_c)(ev.account, ev.criteria);
|
Search(_c)(ev.account, ev.criteria);
|
||||||
|
|
||||||
bool _isFileOfInterest(FileDescriptor file) {
|
bool _isFileOfInterest(FileDescriptor file) {
|
||||||
|
|
434
app/lib/db/entity_converter.dart
Normal file
434
app/lib/db/entity_converter.dart
Normal file
|
@ -0,0 +1,434 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/entity/album.dart';
|
||||||
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
|
import 'package:nc_photos/entity/face_recognition_person.dart';
|
||||||
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/entity/nc_album.dart';
|
||||||
|
import 'package:nc_photos/entity/nc_album_item.dart';
|
||||||
|
import 'package:nc_photos/entity/recognize_face.dart';
|
||||||
|
import 'package:nc_photos/entity/recognize_face_item.dart';
|
||||||
|
import 'package:nc_photos/entity/tag.dart';
|
||||||
|
import 'package:nc_photos/use_case/list_location_group.dart';
|
||||||
|
import 'package:np_api/np_api.dart' as api;
|
||||||
|
import 'package:np_common/object_util.dart';
|
||||||
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
import 'package:np_string/np_string.dart';
|
||||||
|
|
||||||
|
abstract class DbAccountConverter {
|
||||||
|
static DbAccount toDb(Account src) => DbAccount(
|
||||||
|
serverAddress: src.url,
|
||||||
|
userId: src.userId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountExtension on Account {
|
||||||
|
DbAccount toDb() => DbAccountConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
extension AccountListExtension on List<Account> {
|
||||||
|
List<DbAccount> toDb() => map(DbAccountConverter.toDb).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbAlbumConverter {
|
||||||
|
static Album fromDb(File albumFile, DbAlbum src) {
|
||||||
|
return Album(
|
||||||
|
lastUpdated: src.lastUpdated,
|
||||||
|
name: src.name,
|
||||||
|
provider: AlbumProvider.fromJson({
|
||||||
|
"type": src.providerType,
|
||||||
|
"content": src.providerContent,
|
||||||
|
}),
|
||||||
|
coverProvider: AlbumCoverProvider.fromJson({
|
||||||
|
"type": src.coverProviderType,
|
||||||
|
"content": src.coverProviderContent,
|
||||||
|
}),
|
||||||
|
sortProvider: AlbumSortProvider.fromJson({
|
||||||
|
"type": src.sortProviderType,
|
||||||
|
"content": src.sortProviderContent,
|
||||||
|
}),
|
||||||
|
shares: src.shares.isEmpty
|
||||||
|
? null
|
||||||
|
: src.shares
|
||||||
|
.map((e) => AlbumShare(
|
||||||
|
userId: e.userId.toCi(),
|
||||||
|
displayName: e.displayName,
|
||||||
|
sharedAt: e.sharedAt.toUtc(),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
// replace with the original etag when this album was cached
|
||||||
|
albumFile: albumFile.copyWith(etag: OrNull(src.fileEtag)),
|
||||||
|
savedVersion: src.version,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbAlbum toDb(Album src) {
|
||||||
|
final providerJson = src.provider.toJson();
|
||||||
|
final coverProviderJson = src.coverProvider.toJson();
|
||||||
|
final sortProviderJson = src.sortProvider.toJson();
|
||||||
|
return DbAlbum(
|
||||||
|
fileId: src.albumFile!.fileId!,
|
||||||
|
fileEtag: src.albumFile!.etag!,
|
||||||
|
version: Album.version,
|
||||||
|
lastUpdated: src.lastUpdated,
|
||||||
|
name: src.name,
|
||||||
|
providerType: providerJson["type"],
|
||||||
|
providerContent: providerJson["content"],
|
||||||
|
coverProviderType: coverProviderJson["type"],
|
||||||
|
coverProviderContent: coverProviderJson["content"],
|
||||||
|
sortProviderType: sortProviderJson["type"],
|
||||||
|
sortProviderContent: sortProviderJson["content"],
|
||||||
|
shares: src.shares
|
||||||
|
?.map((e) => DbAlbumShare(
|
||||||
|
userId: e.userId.toCaseInsensitiveString(),
|
||||||
|
displayName: e.displayName,
|
||||||
|
sharedAt: e.sharedAt,
|
||||||
|
))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbFaceRecognitionPersonConverter {
|
||||||
|
static FaceRecognitionPerson fromDb(DbFaceRecognitionPerson src) {
|
||||||
|
return FaceRecognitionPerson(
|
||||||
|
name: src.name,
|
||||||
|
thumbFaceId: src.thumbFaceId,
|
||||||
|
count: src.count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbFaceRecognitionPerson toDb(FaceRecognitionPerson src) {
|
||||||
|
return DbFaceRecognitionPerson(
|
||||||
|
name: src.name,
|
||||||
|
thumbFaceId: src.thumbFaceId,
|
||||||
|
count: src.count,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FaceRecognitionPersonExtension on FaceRecognitionPerson {
|
||||||
|
DbFaceRecognitionPerson toDb() => DbFaceRecognitionPersonConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbFileConverter {
|
||||||
|
static File fromDb(String userId, DbFile src) {
|
||||||
|
return File(
|
||||||
|
path: "remote.php/dav/files/$userId/${src.relativePath}",
|
||||||
|
contentLength: src.contentLength,
|
||||||
|
contentType: src.contentType,
|
||||||
|
etag: src.etag,
|
||||||
|
lastModified: src.lastModified,
|
||||||
|
isCollection: src.isCollection,
|
||||||
|
usedBytes: src.usedBytes,
|
||||||
|
hasPreview: src.hasPreview,
|
||||||
|
fileId: src.fileId,
|
||||||
|
isFavorite: src.isFavorite,
|
||||||
|
ownerId: src.ownerId,
|
||||||
|
ownerDisplayName: src.ownerDisplayName,
|
||||||
|
metadata: src.imageData?.let(DbMetadataConverter.fromDb),
|
||||||
|
isArchived: src.isArchived,
|
||||||
|
overrideDateTime: src.overrideDateTime,
|
||||||
|
trashbinFilename: src.trashData?.filename,
|
||||||
|
trashbinOriginalLocation: src.trashData?.originalLocation,
|
||||||
|
trashbinDeletionTime: src.trashData?.deletionTime,
|
||||||
|
location: src.location?.let(DbImageLocationConverter.fromDb),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbFile toDb(File src) {
|
||||||
|
return DbFile(
|
||||||
|
fileId: src.fileId!,
|
||||||
|
contentLength: src.contentLength,
|
||||||
|
contentType: src.contentType,
|
||||||
|
etag: src.etag,
|
||||||
|
lastModified: src.lastModified,
|
||||||
|
isCollection: src.isCollection,
|
||||||
|
usedBytes: src.usedBytes,
|
||||||
|
hasPreview: src.hasPreview,
|
||||||
|
ownerId: src.ownerId,
|
||||||
|
ownerDisplayName: src.ownerDisplayName,
|
||||||
|
relativePath: src.strippedPathWithEmpty,
|
||||||
|
isFavorite: src.isFavorite,
|
||||||
|
isArchived: src.isArchived,
|
||||||
|
overrideDateTime: src.overrideDateTime,
|
||||||
|
bestDateTime: src.bestDateTime,
|
||||||
|
imageData: src.metadata?.let((s) => DbImageData(
|
||||||
|
lastUpdated: s.lastUpdated,
|
||||||
|
fileEtag: s.fileEtag,
|
||||||
|
width: s.imageWidth,
|
||||||
|
height: s.imageHeight,
|
||||||
|
exif: s.exif?.toJson(),
|
||||||
|
exifDateTimeOriginal: s.exif?.dateTimeOriginal,
|
||||||
|
)),
|
||||||
|
location: src.location?.let((s) => DbLocation(
|
||||||
|
version: s.version,
|
||||||
|
name: s.name,
|
||||||
|
latitude: s.latitude,
|
||||||
|
longitude: s.longitude,
|
||||||
|
countryCode: s.countryCode,
|
||||||
|
admin1: s.admin1,
|
||||||
|
admin2: s.admin2,
|
||||||
|
)),
|
||||||
|
trashData: src.trashbinDeletionTime == null
|
||||||
|
? null
|
||||||
|
: DbTrashData(
|
||||||
|
filename: src.trashbinFilename!,
|
||||||
|
originalLocation: src.trashbinOriginalLocation!,
|
||||||
|
deletionTime: src.trashbinDeletionTime!,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbFileDescriptorConverter {
|
||||||
|
static FileDescriptor fromDb(String userId, DbFileDescriptor src) {
|
||||||
|
return FileDescriptor(
|
||||||
|
fdPath: "remote.php/dav/files/$userId/${src.relativePath}",
|
||||||
|
fdId: src.fileId,
|
||||||
|
fdMime: src.contentType,
|
||||||
|
fdIsArchived: src.isArchived ?? false,
|
||||||
|
fdIsFavorite: src.isFavorite ?? false,
|
||||||
|
fdDateTime: src.bestDateTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FileDescriptorExtension on FileDescriptor {
|
||||||
|
DbFileKey toDbKey() => DbFileKey.byId(fdId);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbMetadataConverter {
|
||||||
|
static Metadata fromDb(DbImageData src) {
|
||||||
|
return Metadata(
|
||||||
|
lastUpdated: src.lastUpdated,
|
||||||
|
fileEtag: src.fileEtag,
|
||||||
|
imageWidth: src.width,
|
||||||
|
imageHeight: src.height,
|
||||||
|
exif: src.exif?.let(Exif.fromJson),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbImageData toDb(Metadata src) {
|
||||||
|
return DbImageData(
|
||||||
|
lastUpdated: src.lastUpdated,
|
||||||
|
fileEtag: src.fileEtag,
|
||||||
|
width: src.imageWidth,
|
||||||
|
height: src.imageHeight,
|
||||||
|
exif: src.exif?.toJson(),
|
||||||
|
exifDateTimeOriginal: src.exif?.dateTimeOriginal,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension MetadataExtension on Metadata {
|
||||||
|
DbImageData toDb() => DbMetadataConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbImageLocationConverter {
|
||||||
|
static ImageLocation fromDb(DbLocation src) {
|
||||||
|
return ImageLocation(
|
||||||
|
version: src.version,
|
||||||
|
name: src.name,
|
||||||
|
latitude: src.latitude,
|
||||||
|
longitude: src.longitude,
|
||||||
|
countryCode: src.countryCode,
|
||||||
|
admin1: src.admin1,
|
||||||
|
admin2: src.admin2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbLocation toDb(ImageLocation src) {
|
||||||
|
return DbLocation(
|
||||||
|
version: src.version,
|
||||||
|
name: src.name,
|
||||||
|
latitude: src.latitude,
|
||||||
|
longitude: src.longitude,
|
||||||
|
countryCode: src.countryCode,
|
||||||
|
admin1: src.admin1,
|
||||||
|
admin2: src.admin2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension ImageLocationExtension on ImageLocation {
|
||||||
|
DbLocation toDb() => DbImageLocationConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbLocationGroupConverter {
|
||||||
|
static LocationGroup fromDb(DbLocationGroup src) {
|
||||||
|
return LocationGroup(
|
||||||
|
src.place,
|
||||||
|
src.countryCode,
|
||||||
|
src.count,
|
||||||
|
src.latestFileId,
|
||||||
|
src.latestDateTime,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FileExtension on File {
|
||||||
|
DbFileKey toDbKey() {
|
||||||
|
if (fileId != null) {
|
||||||
|
return DbFileKey.byId(fileId!);
|
||||||
|
} else {
|
||||||
|
return DbFileKey.byPath(strippedPathWithEmpty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DbFile toDb() => DbFileConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbNcAlbumConverter {
|
||||||
|
static NcAlbum fromDb(String userId, DbNcAlbum src) {
|
||||||
|
return NcAlbum(
|
||||||
|
path:
|
||||||
|
"${api.ApiPhotos.path}/$userId/${src.isOwned ? "albums" : "sharedalbums"}/${src.relativePath}",
|
||||||
|
lastPhoto: src.lastPhoto,
|
||||||
|
nbItems: src.nbItems,
|
||||||
|
location: src.location,
|
||||||
|
dateStart: src.dateStart,
|
||||||
|
dateEnd: src.dateEnd,
|
||||||
|
collaborators:
|
||||||
|
src.collaborators.map(NcAlbumCollaborator.fromJson).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbNcAlbum toDb(NcAlbum src) {
|
||||||
|
return DbNcAlbum(
|
||||||
|
relativePath: src.strippedPath,
|
||||||
|
lastPhoto: src.lastPhoto,
|
||||||
|
nbItems: src.nbItems,
|
||||||
|
location: src.location,
|
||||||
|
dateStart: src.dateStart,
|
||||||
|
dateEnd: src.dateEnd,
|
||||||
|
collaborators: src.collaborators.map((e) => e.toJson()).toList(),
|
||||||
|
isOwned: src.isOwned,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension NcAlbumExtension on NcAlbum {
|
||||||
|
DbNcAlbum toDb() => DbNcAlbumConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbNcAlbumItemConverter {
|
||||||
|
static NcAlbumItem fromDb(String userId, String albumRelativePath,
|
||||||
|
bool isAlbumOwned, DbNcAlbumItem src) {
|
||||||
|
return NcAlbumItem(
|
||||||
|
path:
|
||||||
|
"${api.ApiPhotos.path}/$userId/${isAlbumOwned ? "albums" : "sharedalbums"}/$albumRelativePath/${src.relativePath}",
|
||||||
|
fileId: src.fileId,
|
||||||
|
contentLength: src.contentLength,
|
||||||
|
contentType: src.contentType,
|
||||||
|
etag: src.etag,
|
||||||
|
lastModified: src.lastModified,
|
||||||
|
hasPreview: src.hasPreview,
|
||||||
|
isFavorite: src.isFavorite,
|
||||||
|
fileMetadataWidth: src.fileMetadataWidth,
|
||||||
|
fileMetadataHeight: src.fileMetadataHeight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbNcAlbumItem toDb(NcAlbumItem src) {
|
||||||
|
return DbNcAlbumItem(
|
||||||
|
relativePath: src.strippedPath,
|
||||||
|
fileId: src.fileId,
|
||||||
|
contentLength: src.contentLength,
|
||||||
|
contentType: src.contentType,
|
||||||
|
etag: src.etag,
|
||||||
|
lastModified: src.lastModified,
|
||||||
|
hasPreview: src.hasPreview,
|
||||||
|
isFavorite: src.isFavorite,
|
||||||
|
fileMetadataWidth: src.fileMetadataWidth,
|
||||||
|
fileMetadataHeight: src.fileMetadataHeight,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbRecognizeFaceConverter {
|
||||||
|
static RecognizeFace fromDb(DbRecognizeFace src) {
|
||||||
|
return RecognizeFace(label: src.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbRecognizeFace toDb(RecognizeFace src) {
|
||||||
|
return DbRecognizeFace(
|
||||||
|
label: src.label,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension RecognizeFaceExtension on RecognizeFace {
|
||||||
|
DbRecognizeFace toDb() => DbRecognizeFaceConverter.toDb(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbRecognizeFaceItemConverter {
|
||||||
|
static RecognizeFaceItem fromDb(
|
||||||
|
String userId, String faceLabel, DbRecognizeFaceItem src) {
|
||||||
|
return RecognizeFaceItem(
|
||||||
|
path:
|
||||||
|
"${api.ApiRecognize.path}/$userId/faces/$faceLabel/${src.relativePath}",
|
||||||
|
fileId: src.fileId,
|
||||||
|
contentLength: src.contentLength,
|
||||||
|
contentType: src.contentType,
|
||||||
|
etag: src.etag,
|
||||||
|
lastModified: src.lastModified,
|
||||||
|
hasPreview: src.hasPreview,
|
||||||
|
realPath: src.realPath,
|
||||||
|
isFavorite: src.isFavorite,
|
||||||
|
fileMetadataWidth: src.fileMetadataWidth,
|
||||||
|
fileMetadataHeight: src.fileMetadataHeight,
|
||||||
|
faceDetections: src.faceDetections
|
||||||
|
?.let((obj) => (jsonDecode(obj) as List).cast<JsonObj>()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbRecognizeFaceItem toDb(RecognizeFaceItem src) {
|
||||||
|
return DbRecognizeFaceItem(
|
||||||
|
relativePath: src.strippedPath,
|
||||||
|
fileId: src.fileId,
|
||||||
|
contentLength: src.contentLength,
|
||||||
|
contentType: src.contentType,
|
||||||
|
etag: src.etag,
|
||||||
|
lastModified: src.lastModified,
|
||||||
|
hasPreview: src.hasPreview,
|
||||||
|
realPath: src.realPath,
|
||||||
|
isFavorite: src.isFavorite,
|
||||||
|
fileMetadataWidth: src.fileMetadataWidth,
|
||||||
|
fileMetadataHeight: src.fileMetadataHeight,
|
||||||
|
faceDetections: src.faceDetections?.let(jsonEncode),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class DbTagConverter {
|
||||||
|
static Tag fromDb(DbTag src) {
|
||||||
|
return Tag(
|
||||||
|
id: src.id,
|
||||||
|
displayName: src.displayName,
|
||||||
|
userVisible: src.userVisible,
|
||||||
|
userAssignable: src.userAssignable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static DbTag toDb(Tag src) {
|
||||||
|
return DbTag(
|
||||||
|
id: src.id,
|
||||||
|
displayName: src.displayName,
|
||||||
|
userVisible: src.userVisible,
|
||||||
|
userAssignable: src.userAssignable,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TagExtension on Tag {
|
||||||
|
DbTag toDb() => DbTagConverter.toDb(this);
|
||||||
|
}
|
|
@ -10,11 +10,11 @@ import 'package:nc_photos/entity/recognize_face/repo.dart';
|
||||||
import 'package:nc_photos/entity/search.dart';
|
import 'package:nc_photos/entity/search.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/sharee.dart';
|
import 'package:nc_photos/entity/sharee.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
import 'package:nc_photos/entity/tag.dart';
|
||||||
import 'package:nc_photos/entity/tagged_file.dart';
|
import 'package:nc_photos/entity/tagged_file.dart';
|
||||||
import 'package:nc_photos/touch_manager.dart';
|
import 'package:nc_photos/touch_manager.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
enum DiType {
|
enum DiType {
|
||||||
albumRepo,
|
albumRepo,
|
||||||
|
@ -45,8 +45,8 @@ enum DiType {
|
||||||
recognizeFaceRepoRemote,
|
recognizeFaceRepoRemote,
|
||||||
recognizeFaceRepoLocal,
|
recognizeFaceRepoLocal,
|
||||||
pref,
|
pref,
|
||||||
sqliteDb,
|
|
||||||
touchManager,
|
touchManager,
|
||||||
|
npDb,
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiContainer {
|
class DiContainer {
|
||||||
|
@ -79,8 +79,8 @@ class DiContainer {
|
||||||
RecognizeFaceRepo? recognizeFaceRepoRemote,
|
RecognizeFaceRepo? recognizeFaceRepoRemote,
|
||||||
RecognizeFaceRepo? recognizeFaceRepoLocal,
|
RecognizeFaceRepo? recognizeFaceRepoLocal,
|
||||||
Pref? pref,
|
Pref? pref,
|
||||||
sql.SqliteDb? sqliteDb,
|
|
||||||
TouchManager? touchManager,
|
TouchManager? touchManager,
|
||||||
|
NpDb? npDb,
|
||||||
}) : _albumRepo = albumRepo,
|
}) : _albumRepo = albumRepo,
|
||||||
_albumRepoRemote = albumRepoRemote,
|
_albumRepoRemote = albumRepoRemote,
|
||||||
_albumRepoLocal = albumRepoLocal,
|
_albumRepoLocal = albumRepoLocal,
|
||||||
|
@ -109,8 +109,8 @@ class DiContainer {
|
||||||
_recognizeFaceRepoRemote = recognizeFaceRepoRemote,
|
_recognizeFaceRepoRemote = recognizeFaceRepoRemote,
|
||||||
_recognizeFaceRepoLocal = recognizeFaceRepoLocal,
|
_recognizeFaceRepoLocal = recognizeFaceRepoLocal,
|
||||||
_pref = pref,
|
_pref = pref,
|
||||||
_sqliteDb = sqliteDb,
|
_touchManager = touchManager,
|
||||||
_touchManager = touchManager;
|
_npDb = npDb;
|
||||||
|
|
||||||
DiContainer.late();
|
DiContainer.late();
|
||||||
|
|
||||||
|
@ -172,10 +172,10 @@ class DiContainer {
|
||||||
return contianer._recognizeFaceRepoLocal != null;
|
return contianer._recognizeFaceRepoLocal != null;
|
||||||
case DiType.pref:
|
case DiType.pref:
|
||||||
return contianer._pref != null;
|
return contianer._pref != null;
|
||||||
case DiType.sqliteDb:
|
|
||||||
return contianer._sqliteDb != null;
|
|
||||||
case DiType.touchManager:
|
case DiType.touchManager:
|
||||||
return contianer._touchManager != null;
|
return contianer._touchManager != null;
|
||||||
|
case DiType.npDb:
|
||||||
|
return contianer._npDb != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,8 +194,8 @@ class DiContainer {
|
||||||
OrNull<FaceRecognitionPersonRepo>? faceRecognitionPersonRepo,
|
OrNull<FaceRecognitionPersonRepo>? faceRecognitionPersonRepo,
|
||||||
OrNull<RecognizeFaceRepo>? recognizeFaceRepo,
|
OrNull<RecognizeFaceRepo>? recognizeFaceRepo,
|
||||||
OrNull<Pref>? pref,
|
OrNull<Pref>? pref,
|
||||||
OrNull<sql.SqliteDb>? sqliteDb,
|
|
||||||
OrNull<TouchManager>? touchManager,
|
OrNull<TouchManager>? touchManager,
|
||||||
|
OrNull<NpDb>? npDb,
|
||||||
}) {
|
}) {
|
||||||
return DiContainer(
|
return DiContainer(
|
||||||
albumRepo: albumRepo == null ? _albumRepo : albumRepo.obj,
|
albumRepo: albumRepo == null ? _albumRepo : albumRepo.obj,
|
||||||
|
@ -217,8 +217,8 @@ class DiContainer {
|
||||||
? _recognizeFaceRepo
|
? _recognizeFaceRepo
|
||||||
: recognizeFaceRepo.obj,
|
: recognizeFaceRepo.obj,
|
||||||
pref: pref == null ? _pref : pref.obj,
|
pref: pref == null ? _pref : pref.obj,
|
||||||
sqliteDb: sqliteDb == null ? _sqliteDb : sqliteDb.obj,
|
|
||||||
touchManager: touchManager == null ? _touchManager : touchManager.obj,
|
touchManager: touchManager == null ? _touchManager : touchManager.obj,
|
||||||
|
npDb: npDb == null ? _npDb : npDb.obj,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,9 +253,9 @@ class DiContainer {
|
||||||
RecognizeFaceRepo get recognizeFaceRepoRemote => _recognizeFaceRepoRemote!;
|
RecognizeFaceRepo get recognizeFaceRepoRemote => _recognizeFaceRepoRemote!;
|
||||||
RecognizeFaceRepo get recognizeFaceRepoLocal => _recognizeFaceRepoLocal!;
|
RecognizeFaceRepo get recognizeFaceRepoLocal => _recognizeFaceRepoLocal!;
|
||||||
|
|
||||||
sql.SqliteDb get sqliteDb => _sqliteDb!;
|
|
||||||
Pref get pref => _pref!;
|
Pref get pref => _pref!;
|
||||||
TouchManager get touchManager => _touchManager!;
|
TouchManager get touchManager => _touchManager!;
|
||||||
|
NpDb get npDb => _npDb!;
|
||||||
|
|
||||||
set albumRepo(AlbumRepo v) {
|
set albumRepo(AlbumRepo v) {
|
||||||
assert(_albumRepo == null);
|
assert(_albumRepo == null);
|
||||||
|
@ -392,11 +392,6 @@ class DiContainer {
|
||||||
_recognizeFaceRepoLocal = v;
|
_recognizeFaceRepoLocal = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
set sqliteDb(sql.SqliteDb v) {
|
|
||||||
assert(_sqliteDb == null);
|
|
||||||
_sqliteDb = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
set pref(Pref v) {
|
set pref(Pref v) {
|
||||||
assert(_pref == null);
|
assert(_pref == null);
|
||||||
_pref = v;
|
_pref = v;
|
||||||
|
@ -407,6 +402,11 @@ class DiContainer {
|
||||||
_touchManager = v;
|
_touchManager = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set npDb(NpDb v) {
|
||||||
|
assert(_npDb == null);
|
||||||
|
_npDb = v;
|
||||||
|
}
|
||||||
|
|
||||||
AlbumRepo? _albumRepo;
|
AlbumRepo? _albumRepo;
|
||||||
AlbumRepo? _albumRepoRemote;
|
AlbumRepo? _albumRepoRemote;
|
||||||
// Explicitly request a AlbumRepo backed by local source
|
// Explicitly request a AlbumRepo backed by local source
|
||||||
|
@ -438,9 +438,9 @@ class DiContainer {
|
||||||
RecognizeFaceRepo? _recognizeFaceRepoRemote;
|
RecognizeFaceRepo? _recognizeFaceRepoRemote;
|
||||||
RecognizeFaceRepo? _recognizeFaceRepoLocal;
|
RecognizeFaceRepo? _recognizeFaceRepoLocal;
|
||||||
|
|
||||||
sql.SqliteDb? _sqliteDb;
|
|
||||||
Pref? _pref;
|
Pref? _pref;
|
||||||
TouchManager? _touchManager;
|
TouchManager? _touchManager;
|
||||||
|
NpDb? _npDb;
|
||||||
}
|
}
|
||||||
|
|
||||||
extension DiContainerExtension on DiContainer {
|
extension DiContainerExtension on DiContainer {
|
||||||
|
|
|
@ -6,11 +6,11 @@ import 'package:nc_photos/entity/album/data_source2.dart';
|
||||||
import 'package:nc_photos/entity/album/repo2.dart';
|
import 'package:nc_photos/entity/album/repo2.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/exception_event.dart';
|
import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
|
||||||
|
@ -90,7 +90,7 @@ class AlbumSqliteDbDataSource implements AlbumDataSource {
|
||||||
_log.info(
|
_log.info(
|
||||||
"[getAll] ${albumFiles.map((f) => f.filename).toReadableString()}");
|
"[getAll] ${albumFiles.map((f) => f.filename).toReadableString()}");
|
||||||
final failed = <String, Map>{};
|
final failed = <String, Map>{};
|
||||||
final albums = await AlbumSqliteDbDataSource2(_c.sqliteDb).getAlbums(
|
final albums = await AlbumSqliteDbDataSource2(_c.npDb).getAlbums(
|
||||||
account,
|
account,
|
||||||
albumFiles,
|
albumFiles,
|
||||||
onError: (v, error, stackTrace) {
|
onError: (v, error, stackTrace) {
|
||||||
|
@ -119,13 +119,13 @@ class AlbumSqliteDbDataSource implements AlbumDataSource {
|
||||||
@override
|
@override
|
||||||
create(Account account, Album album) async {
|
create(Account account, Album album) async {
|
||||||
_log.info("[create]");
|
_log.info("[create]");
|
||||||
return AlbumSqliteDbDataSource2(_c.sqliteDb).create(account, album);
|
return AlbumSqliteDbDataSource2(_c.npDb).create(account, album);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
update(Account account, Album album) async {
|
update(Account account, Album album) async {
|
||||||
_log.info("[update] ${album.albumFile!.path}");
|
_log.info("[update] ${album.albumFile!.path}");
|
||||||
return AlbumSqliteDbDataSource2(_c.sqliteDb).update(account, album);
|
return AlbumSqliteDbDataSource2(_c.npDb).update(account, album);
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
@ -134,7 +134,7 @@ class AlbumSqliteDbDataSource implements AlbumDataSource {
|
||||||
/// Backward compatibility only, use [CachedAlbumRepo2] instead
|
/// Backward compatibility only, use [CachedAlbumRepo2] instead
|
||||||
@npLog
|
@npLog
|
||||||
class AlbumCachedDataSource implements AlbumDataSource {
|
class AlbumCachedDataSource implements AlbumDataSource {
|
||||||
AlbumCachedDataSource(DiContainer c) : sqliteDb = c.sqliteDb;
|
AlbumCachedDataSource(DiContainer c) : npDb = c.npDb;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
get(Account account, File albumFile) async {
|
get(Account account, File albumFile) async {
|
||||||
|
@ -146,7 +146,7 @@ class AlbumCachedDataSource implements AlbumDataSource {
|
||||||
getAll(Account account, List<File> albumFiles) async* {
|
getAll(Account account, List<File> albumFiles) async* {
|
||||||
final repo = CachedAlbumRepo2(
|
final repo = CachedAlbumRepo2(
|
||||||
const AlbumRemoteDataSource2(),
|
const AlbumRemoteDataSource2(),
|
||||||
AlbumSqliteDbDataSource2(sqliteDb),
|
AlbumSqliteDbDataSource2(npDb),
|
||||||
);
|
);
|
||||||
final albums = await repo.getAlbums(account, albumFiles).last;
|
final albums = await repo.getAlbums(account, albumFiles).last;
|
||||||
for (final a in albums) {
|
for (final a in albums) {
|
||||||
|
@ -158,7 +158,7 @@ class AlbumCachedDataSource implements AlbumDataSource {
|
||||||
update(Account account, Album album) {
|
update(Account account, Album album) {
|
||||||
return CachedAlbumRepo2(
|
return CachedAlbumRepo2(
|
||||||
const AlbumRemoteDataSource2(),
|
const AlbumRemoteDataSource2(),
|
||||||
AlbumSqliteDbDataSource2(sqliteDb),
|
AlbumSqliteDbDataSource2(npDb),
|
||||||
).update(account, album);
|
).update(account, album);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,9 +166,9 @@ class AlbumCachedDataSource implements AlbumDataSource {
|
||||||
create(Account account, Album album) {
|
create(Account account, Album album) {
|
||||||
return CachedAlbumRepo2(
|
return CachedAlbumRepo2(
|
||||||
const AlbumRemoteDataSource2(),
|
const AlbumRemoteDataSource2(),
|
||||||
AlbumSqliteDbDataSource2(sqliteDb),
|
AlbumSqliteDbDataSource2(npDb),
|
||||||
).create(account, album);
|
).create(account, album);
|
||||||
}
|
}
|
||||||
|
|
||||||
final sql.SqliteDb sqliteDb;
|
final NpDb npDb;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,26 +3,26 @@ import 'dart:math';
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/repo2.dart';
|
import 'package:nc_photos/entity/album/repo2.dart';
|
||||||
import 'package:nc_photos/entity/album/upgrader.dart';
|
import 'package:nc_photos/entity/album/upgrader.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart' as sql;
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/get_file_binary.dart';
|
import 'package:nc_photos/use_case/get_file_binary.dart';
|
||||||
import 'package:nc_photos/use_case/ls_single_file.dart';
|
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||||
import 'package:nc_photos/use_case/put_file_binary.dart';
|
import 'package:nc_photos/use_case/put_file_binary.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_collection/np_collection.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'data_source2.g.dart';
|
part 'data_source2.g.dart';
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ class AlbumRemoteDataSource2 implements AlbumDataSource2 {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class AlbumSqliteDbDataSource2 implements AlbumDataSource2 {
|
class AlbumSqliteDbDataSource2 implements AlbumDataSource2 {
|
||||||
const AlbumSqliteDbDataSource2(this.sqliteDb);
|
const AlbumSqliteDbDataSource2(this.npDb);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<Album>> getAlbums(
|
Future<List<Album>> getAlbums(
|
||||||
|
@ -121,73 +121,38 @@ class AlbumSqliteDbDataSource2 implements AlbumDataSource2 {
|
||||||
List<File> albumFiles, {
|
List<File> albumFiles, {
|
||||||
ErrorWithValueHandler<File>? onError,
|
ErrorWithValueHandler<File>? onError,
|
||||||
}) async {
|
}) async {
|
||||||
late final List<sql.CompleteFile> dbFiles;
|
final albums = await npDb.getAlbumsByAlbumFileIds(
|
||||||
late final List<sql.AlbumWithShare> albumWithShares;
|
account: account.toDb(),
|
||||||
await sqliteDb.use((db) async {
|
fileIds: albumFiles.map((e) => e.fileId!).toList(),
|
||||||
dbFiles = await db.completeFilesByFileIds(
|
);
|
||||||
albumFiles.map((f) => f.fileId!),
|
final files = await npDb.getFilesByFileIds(
|
||||||
appAccount: account,
|
account: account.toDb(),
|
||||||
);
|
fileIds: albums.map((e) => e.fileId).toList(),
|
||||||
final query = db.select(db.albums).join([
|
);
|
||||||
sql.leftOuterJoin(
|
final albumMap = albums.map((e) => MapEntry(e.fileId, e)).toMap();
|
||||||
db.albumShares, db.albumShares.album.equalsExp(db.albums.rowId)),
|
final fileMap = files.map((e) => MapEntry(e.fileId, e)).toMap();
|
||||||
])
|
|
||||||
..where(db.albums.file.isIn(dbFiles.map((f) => f.file.rowId)));
|
|
||||||
albumWithShares = await query
|
|
||||||
.map((r) => sql.AlbumWithShare(
|
|
||||||
r.readTable(db.albums), r.readTableOrNull(db.albumShares)))
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
|
|
||||||
// group entries together
|
|
||||||
final fileRowIdMap = <int, sql.CompleteFile>{};
|
|
||||||
for (var f in dbFiles) {
|
|
||||||
fileRowIdMap[f.file.rowId] = f;
|
|
||||||
}
|
|
||||||
final fileIdMap = <int, Map>{};
|
|
||||||
for (final s in albumWithShares) {
|
|
||||||
final f = fileRowIdMap[s.album.file];
|
|
||||||
if (f == null) {
|
|
||||||
_log.severe(
|
|
||||||
"[getAlbums] File missing for album (rowId: ${s.album.rowId}");
|
|
||||||
} else {
|
|
||||||
fileIdMap[f.file.fileId] ??= {
|
|
||||||
"file": f,
|
|
||||||
"album": s.album,
|
|
||||||
};
|
|
||||||
if (s.share != null) {
|
|
||||||
(fileIdMap[f.file.fileId]!["shares"] ??= <sql.AlbumShare>[])
|
|
||||||
.add(s.share!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sort as the input list
|
|
||||||
return albumFiles
|
return albumFiles
|
||||||
.map((f) {
|
.map((f) {
|
||||||
final item = fileIdMap[f.fileId];
|
var dbAlbum = albumMap[f.fileId];
|
||||||
if (item == null) {
|
final dbFile = fileMap[f.fileId];
|
||||||
|
if (dbAlbum == null || dbFile == null) {
|
||||||
// cache not found
|
// cache not found
|
||||||
onError?.call(
|
onError?.call(
|
||||||
f, const CacheNotFoundException(), StackTrace.current);
|
f, const CacheNotFoundException(), StackTrace.current);
|
||||||
return null;
|
return null;
|
||||||
} else {
|
}
|
||||||
try {
|
try {
|
||||||
final queriedFile = sql.SqliteFileConverter.fromSql(
|
final file =
|
||||||
account.userId.toString(), item["file"]);
|
DbFileConverter.fromDb(account.userId.toString(), dbFile);
|
||||||
var dbAlbum = item["album"] as sql.Album;
|
if (dbAlbum.version < 9) {
|
||||||
if (dbAlbum.version < 9) {
|
dbAlbum = AlbumUpgraderV8(logFilePath: file.path).doDb(dbAlbum)!;
|
||||||
dbAlbum = AlbumUpgraderV8(logFilePath: queriedFile.path)
|
|
||||||
.doDb(dbAlbum)!;
|
|
||||||
}
|
|
||||||
return sql.SqliteAlbumConverter.fromSql(
|
|
||||||
dbAlbum, queriedFile, item["shares"] ?? []);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.severe("[getAlbums] Failed while converting DB entry", e,
|
|
||||||
stackTrace);
|
|
||||||
onError?.call(f, e, stackTrace);
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
return DbAlbumConverter.fromDb(file, dbAlbum);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe(
|
||||||
|
"[getAlbums] Failed while converting DB entry", e, stackTrace);
|
||||||
|
onError?.call(f, e, stackTrace);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.whereNotNull()
|
.whereNotNull()
|
||||||
|
@ -203,50 +168,12 @@ class AlbumSqliteDbDataSource2 implements AlbumDataSource2 {
|
||||||
@override
|
@override
|
||||||
Future<void> update(Account account, Album album) async {
|
Future<void> update(Account account, Album album) async {
|
||||||
_log.info("[update] ${album.albumFile!.path}");
|
_log.info("[update] ${album.albumFile!.path}");
|
||||||
await sqliteDb.use((db) async {
|
await npDb.syncAlbum(
|
||||||
final rowIds =
|
account: account.toDb(),
|
||||||
await db.accountFileRowIdsOf(album.albumFile!, appAccount: account);
|
albumFile: DbFileConverter.toDb(album.albumFile!),
|
||||||
final insert = sql.SqliteAlbumConverter.toSql(
|
album: DbAlbumConverter.toDb(album),
|
||||||
album, rowIds.fileRowId, album.albumFile!.etag!);
|
);
|
||||||
var rowId = await _updateCache(db, rowIds.fileRowId, insert.album);
|
|
||||||
if (rowId == null) {
|
|
||||||
// new album, need insert
|
|
||||||
_log.info("[update] Insert new album");
|
|
||||||
final insertedAlbum =
|
|
||||||
await db.into(db.albums).insertReturning(insert.album);
|
|
||||||
rowId = insertedAlbum.rowId;
|
|
||||||
} else {
|
|
||||||
await (db.delete(db.albumShares)..where((t) => t.album.equals(rowId!)))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
if (insert.albumShares.isNotEmpty) {
|
|
||||||
await db.batch((batch) {
|
|
||||||
batch.insertAll(
|
|
||||||
db.albumShares,
|
|
||||||
insert.albumShares.map((s) => s.copyWith(album: sql.Value(rowId!))),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int?> _updateCache(
|
final NpDb npDb;
|
||||||
sql.SqliteDb db, int dbFileRowId, sql.AlbumsCompanion dbAlbum) async {
|
|
||||||
final rowIdQuery = db.selectOnly(db.albums)
|
|
||||||
..addColumns([db.albums.rowId])
|
|
||||||
..where(db.albums.file.equals(dbFileRowId))
|
|
||||||
..limit(1);
|
|
||||||
final rowId =
|
|
||||||
await rowIdQuery.map((r) => r.read(db.albums.rowId)!).getSingleOrNull();
|
|
||||||
if (rowId == null) {
|
|
||||||
// new album
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
await (db.update(db.albums)..where((t) => t.rowId.equals(rowId)))
|
|
||||||
.write(dbAlbum);
|
|
||||||
return rowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
final sql.SqliteDb sqliteDb;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
import 'package:nc_photos/object_extension.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
@ -17,7 +15,7 @@ part 'upgrader.g.dart';
|
||||||
|
|
||||||
abstract class AlbumUpgrader {
|
abstract class AlbumUpgrader {
|
||||||
JsonObj? doJson(JsonObj json);
|
JsonObj? doJson(JsonObj json);
|
||||||
sql.Album? doDb(sql.Album dbObj);
|
DbAlbum? doDb(DbAlbum dbObj);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upgrade v1 Album to v2
|
/// Upgrade v1 Album to v2
|
||||||
|
@ -37,7 +35,7 @@ class AlbumUpgraderV1 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
/// File path for logging only
|
/// File path for logging only
|
||||||
final String? logFilePath;
|
final String? logFilePath;
|
||||||
|
@ -72,7 +70,7 @@ class AlbumUpgraderV2 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
/// File path for logging only
|
/// File path for logging only
|
||||||
final String? logFilePath;
|
final String? logFilePath;
|
||||||
|
@ -101,7 +99,7 @@ class AlbumUpgraderV3 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
/// File path for logging only
|
/// File path for logging only
|
||||||
final String? logFilePath;
|
final String? logFilePath;
|
||||||
|
@ -172,7 +170,7 @@ class AlbumUpgraderV4 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
/// File path for logging only
|
/// File path for logging only
|
||||||
final String? logFilePath;
|
final String? logFilePath;
|
||||||
|
@ -216,7 +214,7 @@ class AlbumUpgraderV5 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
final Account account;
|
final Account account;
|
||||||
final File? albumFile;
|
final File? albumFile;
|
||||||
|
@ -239,7 +237,7 @@ class AlbumUpgraderV6 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
/// File path for logging only
|
/// File path for logging only
|
||||||
final String? logFilePath;
|
final String? logFilePath;
|
||||||
|
@ -259,7 +257,7 @@ class AlbumUpgraderV7 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) => null;
|
DbAlbum? doDb(DbAlbum dbObj) => null;
|
||||||
|
|
||||||
/// File path for logging only
|
/// File path for logging only
|
||||||
final String? logFilePath;
|
final String? logFilePath;
|
||||||
|
@ -302,32 +300,30 @@ class AlbumUpgraderV8 implements AlbumUpgrader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
sql.Album? doDb(sql.Album dbObj) {
|
DbAlbum? doDb(DbAlbum dbObj) {
|
||||||
_log.fine("[doDb] Upgrade v8 Album for file: $logFilePath");
|
_log.fine("[doDb] Upgrade v8 Album for file: $logFilePath");
|
||||||
if (dbObj.coverProviderType == "manual") {
|
if (dbObj.coverProviderType == "manual") {
|
||||||
final content = (jsonDecode(dbObj.coverProviderContent) as Map)
|
final content = dbObj.coverProviderContent;
|
||||||
.cast<String, dynamic>();
|
|
||||||
final converted = _fileJsonToFileDescriptorJson(
|
final converted = _fileJsonToFileDescriptorJson(
|
||||||
(content["coverFile"] as Map).cast<String, dynamic>());
|
(content["coverFile"] as Map).cast<String, dynamic>());
|
||||||
if (converted["fdId"] != null) {
|
if (converted["fdId"] != null) {
|
||||||
return dbObj.copyWith(
|
return dbObj.copyWith(
|
||||||
coverProviderContent: jsonEncode({"coverFile": converted}),
|
coverProviderContent: {"coverFile": converted},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return dbObj.copyWith(coverProviderContent: "{}");
|
return dbObj.copyWith(coverProviderContent: const {});
|
||||||
}
|
}
|
||||||
} else if (dbObj.coverProviderType == "auto") {
|
} else if (dbObj.coverProviderType == "auto") {
|
||||||
final content = (jsonDecode(dbObj.coverProviderContent) as Map)
|
final content = dbObj.coverProviderContent;
|
||||||
.cast<String, dynamic>();
|
|
||||||
if (content["coverFile"] != null) {
|
if (content["coverFile"] != null) {
|
||||||
final converted = _fileJsonToFileDescriptorJson(
|
final converted = _fileJsonToFileDescriptorJson(
|
||||||
(content["coverFile"] as Map).cast<String, dynamic>());
|
(content["coverFile"] as Map).cast<String, dynamic>());
|
||||||
if (converted["fdId"] != null) {
|
if (converted["fdId"] != null) {
|
||||||
return dbObj.copyWith(
|
return dbObj.copyWith(
|
||||||
coverProviderContent: jsonEncode({"coverFile": converted}),
|
coverProviderContent: {"coverFile": converted},
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return dbObj.copyWith(coverProviderContent: "{}");
|
return dbObj.copyWith(coverProviderContent: const {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,9 @@ class CollectionLocationGroupAdapter
|
||||||
CollectionAdapterUnshareableTag
|
CollectionAdapterUnshareableTag
|
||||||
implements CollectionAdapter {
|
implements CollectionAdapter {
|
||||||
CollectionLocationGroupAdapter(this._c, this.account, this.collection)
|
CollectionLocationGroupAdapter(this._c, this.account, this.collection)
|
||||||
: assert(require(_c)),
|
: _provider =
|
||||||
_provider =
|
|
||||||
collection.contentProvider as CollectionLocationGroupProvider;
|
collection.contentProvider as CollectionLocationGroupProvider;
|
||||||
|
|
||||||
static bool require(DiContainer c) => ListLocationFile.require(c);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<CollectionItem>> listItem() async* {
|
Stream<List<CollectionItem>> listItem() async* {
|
||||||
final files = <File>[];
|
final files = <File>[];
|
||||||
|
|
|
@ -9,7 +9,6 @@ import 'package:nc_photos/entity/collection_item/basic_item.dart';
|
||||||
import 'package:nc_photos/entity/file/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.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/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/use_case/list_location_file.dart';
|
|
||||||
|
|
||||||
class CollectionMemoryAdapter
|
class CollectionMemoryAdapter
|
||||||
with
|
with
|
||||||
|
@ -18,10 +17,7 @@ class CollectionMemoryAdapter
|
||||||
CollectionAdapterUnshareableTag
|
CollectionAdapterUnshareableTag
|
||||||
implements CollectionAdapter {
|
implements CollectionAdapter {
|
||||||
CollectionMemoryAdapter(this._c, this.account, this.collection)
|
CollectionMemoryAdapter(this._c, this.account, this.collection)
|
||||||
: assert(require(_c)),
|
: _provider = collection.contentProvider as CollectionMemoryProvider;
|
||||||
_provider = collection.contentProvider as CollectionMemoryProvider;
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => ListLocationFile.require(c);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<CollectionItem>> listItem() async* {
|
Stream<List<CollectionItem>> listItem() async* {
|
||||||
|
|
|
@ -36,8 +36,7 @@ class CollectionNcAlbumAdapter
|
||||||
: assert(require(_c)),
|
: assert(require(_c)),
|
||||||
_provider = collection.contentProvider as CollectionNcAlbumProvider;
|
_provider = collection.contentProvider as CollectionNcAlbumProvider;
|
||||||
|
|
||||||
static bool require(DiContainer c) =>
|
static bool require(DiContainer c) => ListNcAlbumItem.require(c);
|
||||||
ListNcAlbumItem.require(c) && FindFileDescriptor.require(c);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<CollectionItem>> listItem() {
|
Stream<List<CollectionItem>> listItem() {
|
||||||
|
|
|
@ -2,15 +2,15 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/entity_converter.dart';
|
import 'package:nc_photos/api/entity_converter.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_face.dart';
|
import 'package:nc_photos/entity/face_recognition_face.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_person.dart';
|
import 'package:nc_photos/entity/face_recognition_person.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_person/repo.dart';
|
import 'package:nc_photos/entity/face_recognition_person/repo.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:np_api/np_api.dart' as api;
|
import 'package:np_api/np_api.dart' as api;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
|
||||||
|
@ -66,18 +66,16 @@ class FaceRecognitionPersonRemoteDataSource
|
||||||
@npLog
|
@npLog
|
||||||
class FaceRecognitionPersonSqliteDbDataSource
|
class FaceRecognitionPersonSqliteDbDataSource
|
||||||
implements FaceRecognitionPersonDataSource {
|
implements FaceRecognitionPersonDataSource {
|
||||||
const FaceRecognitionPersonSqliteDbDataSource(this.sqliteDb);
|
const FaceRecognitionPersonSqliteDbDataSource(this.db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<FaceRecognitionPerson>> getPersons(Account account) async {
|
Future<List<FaceRecognitionPerson>> getPersons(Account account) async {
|
||||||
_log.info("[getPersons] $account");
|
_log.info("[getPersons] $account");
|
||||||
final dbPersons = await sqliteDb.use((db) async {
|
final results = await db.getFaceRecognitionPersons(account: account.toDb());
|
||||||
return await db.allFaceRecognitionPersons(account: sql.ByAccount.app(account));
|
return results
|
||||||
});
|
|
||||||
return dbPersons
|
|
||||||
.map((p) {
|
.map((p) {
|
||||||
try {
|
try {
|
||||||
return SqliteFaceRecognitionPersonConverter.fromSql(p);
|
return DbFaceRecognitionPersonConverter.fromDb(p);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe(
|
_log.severe(
|
||||||
"[getPersons] Failed while converting DB entry", e, stackTrace);
|
"[getPersons] Failed while converting DB entry", e, stackTrace);
|
||||||
|
@ -97,5 +95,5 @@ class FaceRecognitionPersonSqliteDbDataSource
|
||||||
.getFaces(account, person);
|
.getFaces(account, person);
|
||||||
}
|
}
|
||||||
|
|
||||||
final sql.SqliteDb sqliteDb;
|
final NpDb db;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,26 +1,26 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/entity_converter.dart';
|
import 'package:nc_photos/api/entity_converter.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.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/file_cache_manager.dart';
|
import 'package:nc_photos/entity/file/file_cache_manager.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.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/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/use_case/compat/v32.dart';
|
import 'package:nc_photos/use_case/compat/v32.dart';
|
||||||
import 'package:np_api/np_api.dart' as api;
|
import 'package:np_api/np_api.dart' as api;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_datetime/np_datetime.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:path/path.dart' as path_lib;
|
import 'package:path/path.dart' as path_lib;
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
@ -363,23 +363,22 @@ class FileWebdavDataSource implements FileDataSource {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class FileSqliteDbDataSource implements FileDataSource {
|
class FileSqliteDbDataSource implements FileDataSource {
|
||||||
FileSqliteDbDataSource(this._c);
|
const FileSqliteDbDataSource(this._c);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
list(Account account, File dir) async {
|
Future<List<File>> list(Account account, File dir) async {
|
||||||
_log.info("[list] ${dir.path}");
|
_log.info("[list] ${dir.path}");
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final List<DbFile> dbFiles;
|
||||||
final dbAccount = await db.accountOf(account);
|
try {
|
||||||
final sql.File dbDir;
|
dbFiles = await _c.npDb.getFilesByDirKey(
|
||||||
try {
|
account: account.toDb(),
|
||||||
dbDir = await db.fileOf(dir, sqlAccount: dbAccount);
|
dir: dir.toDbKey(),
|
||||||
} catch (_) {
|
);
|
||||||
throw CacheNotFoundException("No entry: ${dir.path}");
|
} on DbNotFoundException catch (_) {
|
||||||
}
|
throw CacheNotFoundException("No entry: ${dir.path}");
|
||||||
return await db.completeFilesByDirRowId(dbDir.rowId,
|
}
|
||||||
sqlAccount: dbAccount);
|
final results = dbFiles
|
||||||
});
|
.map((f) => DbFileConverter.fromDb(account.userId.toString(), f))
|
||||||
final results = (await dbFiles.convertToAppFile(account))
|
|
||||||
.where((f) => _validateFile(f))
|
.where((f) => _validateFile(f))
|
||||||
.toList();
|
.toList();
|
||||||
_log.fine("[list] Queried ${results.length} files");
|
_log.fine("[list] Queried ${results.length} files");
|
||||||
|
@ -405,33 +404,17 @@ class FileSqliteDbDataSource 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 dbFiles = await _c.sqliteDb.use((db) async {
|
final results = await _c.npDb.getFilesByTimeRange(
|
||||||
final query = db.queryFiles().run((q) {
|
account: account.toDb(),
|
||||||
q.setQueryMode(sql.FilesQueryMode.completeFile);
|
dirRoots: account.roots,
|
||||||
q.setAppAccount(account);
|
range: TimeRange(
|
||||||
for (final r in account.roots) {
|
from: DateTime.fromMillisecondsSinceEpoch(fromEpochMs),
|
||||||
if (r.isNotEmpty) {
|
to: DateTime.fromMillisecondsSinceEpoch(toEpochMs),
|
||||||
q.byOrRelativePathPattern("$r/%");
|
),
|
||||||
}
|
);
|
||||||
}
|
return results
|
||||||
return q.build();
|
.map((e) => DbFileConverter.fromDb(account.userId.toString(), e))
|
||||||
});
|
.toList();
|
||||||
final dateTime = db.accountFiles.bestDateTime.unixepoch;
|
|
||||||
query
|
|
||||||
..where(dateTime.isBetweenValues(
|
|
||||||
fromEpochMs ~/ 1000, (toEpochMs ~/ 1000) - 1))
|
|
||||||
..orderBy([sql.OrderingTerm.desc(dateTime)]);
|
|
||||||
return await query
|
|
||||||
.map((r) => sql.CompleteFile(
|
|
||||||
r.readTable(db.files),
|
|
||||||
r.readTable(db.accountFiles),
|
|
||||||
r.readTableOrNull(db.images),
|
|
||||||
r.readTableOrNull(db.imageLocations),
|
|
||||||
r.readTableOrNull(db.trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
return await dbFiles.convertToAppFile(account);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -453,7 +436,7 @@ class FileSqliteDbDataSource implements FileDataSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
updateProperty(
|
Future<void> updateProperty(
|
||||||
Account account,
|
Account account,
|
||||||
File f, {
|
File f, {
|
||||||
OrNull<Metadata>? metadata,
|
OrNull<Metadata>? metadata,
|
||||||
|
@ -463,79 +446,26 @@ class FileSqliteDbDataSource implements FileDataSource {
|
||||||
OrNull<ImageLocation>? location,
|
OrNull<ImageLocation>? location,
|
||||||
}) async {
|
}) async {
|
||||||
_log.info("[updateProperty] ${f.path}");
|
_log.info("[updateProperty] ${f.path}");
|
||||||
await _c.sqliteDb.use((db) async {
|
await _c.npDb.updateFileByFileId(
|
||||||
final rowIds = await db.accountFileRowIdsOf(f, appAccount: account);
|
account: account.toDb(),
|
||||||
if (isArchived != null ||
|
fileId: f.fileId!,
|
||||||
overrideDateTime != null ||
|
isFavorite: favorite?.let(OrNull.new),
|
||||||
favorite != null ||
|
isArchived: isArchived,
|
||||||
metadata != null) {
|
overrideDateTime: overrideDateTime,
|
||||||
final update = sql.AccountFilesCompanion(
|
bestDateTime: overrideDateTime == null && metadata == null
|
||||||
isArchived: isArchived == null
|
? null
|
||||||
? const sql.Value.absent()
|
: file_util.getBestDateTime(
|
||||||
: sql.Value(isArchived.obj),
|
overrideDateTime: overrideDateTime == null
|
||||||
overrideDateTime: overrideDateTime == null
|
? f.overrideDateTime
|
||||||
? const sql.Value.absent()
|
: overrideDateTime.obj,
|
||||||
: sql.Value(overrideDateTime.obj),
|
dateTimeOriginal: metadata == null
|
||||||
isFavorite:
|
? f.metadata?.exif?.dateTimeOriginal
|
||||||
favorite == null ? const sql.Value.absent() : sql.Value(favorite),
|
: metadata.obj?.exif?.dateTimeOriginal,
|
||||||
bestDateTime: overrideDateTime == null && metadata == null
|
lastModified: f.lastModified,
|
||||||
? const sql.Value.absent()
|
),
|
||||||
: sql.Value(file_util.getBestDateTime(
|
imageData: metadata?.let((e) => OrNull(e.obj?.toDb())),
|
||||||
overrideDateTime: overrideDateTime == null
|
location: location?.let((e) => OrNull(e.obj?.toDb())),
|
||||||
? f.overrideDateTime
|
);
|
||||||
: overrideDateTime.obj,
|
|
||||||
dateTimeOriginal: metadata == null
|
|
||||||
? f.metadata?.exif?.dateTimeOriginal
|
|
||||||
: metadata.obj?.exif?.dateTimeOriginal,
|
|
||||||
lastModified: f.lastModified,
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
await (db.update(db.accountFiles)
|
|
||||||
..where((t) => t.rowId.equals(rowIds.accountFileRowId)))
|
|
||||||
.write(update);
|
|
||||||
}
|
|
||||||
if (metadata != null) {
|
|
||||||
if (metadata.obj == null) {
|
|
||||||
await (db.delete(db.images)
|
|
||||||
..where((t) => t.accountFile.equals(rowIds.accountFileRowId)))
|
|
||||||
.go();
|
|
||||||
} else {
|
|
||||||
await db
|
|
||||||
.into(db.images)
|
|
||||||
.insertOnConflictUpdate(sql.ImagesCompanion.insert(
|
|
||||||
accountFile: sql.Value(rowIds.accountFileRowId),
|
|
||||||
lastUpdated: metadata.obj!.lastUpdated,
|
|
||||||
fileEtag: sql.Value(metadata.obj!.fileEtag),
|
|
||||||
width: sql.Value(metadata.obj!.imageWidth),
|
|
||||||
height: sql.Value(metadata.obj!.imageHeight),
|
|
||||||
exifRaw: sql.Value(
|
|
||||||
metadata.obj!.exif?.toJson().run((j) => jsonEncode(j))),
|
|
||||||
dateTimeOriginal:
|
|
||||||
sql.Value(metadata.obj!.exif?.dateTimeOriginal),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (location != null) {
|
|
||||||
if (location.obj == null) {
|
|
||||||
await (db.delete(db.imageLocations)
|
|
||||||
..where((t) => t.accountFile.equals(rowIds.accountFileRowId)))
|
|
||||||
.go();
|
|
||||||
} else {
|
|
||||||
await db
|
|
||||||
.into(db.imageLocations)
|
|
||||||
.insertOnConflictUpdate(sql.ImageLocationsCompanion.insert(
|
|
||||||
accountFile: sql.Value(rowIds.accountFileRowId),
|
|
||||||
version: location.obj!.version,
|
|
||||||
name: sql.Value(location.obj!.name),
|
|
||||||
latitude: sql.Value(location.obj!.latitude),
|
|
||||||
longitude: sql.Value(location.obj!.longitude),
|
|
||||||
countryCode: sql.Value(location.obj!.countryCode),
|
|
||||||
admin1: sql.Value(location.obj!.admin1),
|
|
||||||
admin2: sql.Value(location.obj!.admin2),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -554,15 +484,13 @@ class FileSqliteDbDataSource implements FileDataSource {
|
||||||
File f,
|
File f,
|
||||||
String destination, {
|
String destination, {
|
||||||
bool? shouldOverwrite,
|
bool? shouldOverwrite,
|
||||||
}) async {
|
}) {
|
||||||
_log.info("[move] ${f.path} to $destination");
|
_log.info("[move] ${f.path} to $destination");
|
||||||
await _c.sqliteDb.use((db) async {
|
return _c.npDb.updateFileByFileId(
|
||||||
await db.moveFileByFileId(
|
account: account.toDb(),
|
||||||
sql.ByAccount.app(account),
|
fileId: f.fileId!,
|
||||||
f.fileId!,
|
relativePath: File(path: destination).strippedPathWithEmpty,
|
||||||
File(path: destination).strippedPathWithEmpty,
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -603,7 +531,7 @@ class FileCachedDataSource implements FileDataSource {
|
||||||
}) : _sqliteDbSrc = FileSqliteDbDataSource(_c);
|
}) : _sqliteDbSrc = FileSqliteDbDataSource(_c);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
list(Account account, File dir) async {
|
Future<List<File>> list(Account account, File dir) async {
|
||||||
final cacheLoader = FileCacheLoader(
|
final cacheLoader = FileCacheLoader(
|
||||||
_c,
|
_c,
|
||||||
cacheSrc: _sqliteDbSrc,
|
cacheSrc: _sqliteDbSrc,
|
||||||
|
|
|
@ -1,20 +1,13 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.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/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
|
||||||
|
|
||||||
part 'file_cache_manager.g.dart';
|
part 'file_cache_manager.g.dart';
|
||||||
|
|
||||||
|
@ -88,9 +81,7 @@ class FileCacheLoader {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class FileSqliteCacheUpdater {
|
class FileSqliteCacheUpdater {
|
||||||
FileSqliteCacheUpdater(this._c) : assert(require(_c));
|
const FileSqliteCacheUpdater(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
Future<void> call(
|
Future<void> call(
|
||||||
Account account,
|
Account account,
|
||||||
|
@ -99,335 +90,50 @@ class FileSqliteCacheUpdater {
|
||||||
}) async {
|
}) async {
|
||||||
final s = Stopwatch()..start();
|
final s = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
await _cacheRemote(account, dir, remote);
|
await _c.npDb.syncDirFiles(
|
||||||
|
account: account.toDb(),
|
||||||
|
dirFile: dir.toDbKey(),
|
||||||
|
files: remote.map((e) => e.toDb()).toList(),
|
||||||
|
);
|
||||||
} finally {
|
} finally {
|
||||||
_log.info("[call] Elapsed time: ${s.elapsedMilliseconds}ms");
|
_log.info("[call] Elapsed time: ${s.elapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateSingle(Account account, File remoteFile) async {
|
Future<void> updateSingle(Account account, File remoteFile) async {
|
||||||
final sqlFile = SqliteFileConverter.toSql(null, remoteFile);
|
await _c.npDb.syncFile(
|
||||||
await _c.sqliteDb.use((db) async {
|
account: account.toDb(),
|
||||||
final dbAccount = await db.accountOf(account);
|
file: remoteFile.toDb(),
|
||||||
final inserts =
|
);
|
||||||
await _updateCache(db, dbAccount, [sqlFile], [remoteFile], null);
|
|
||||||
if (inserts.isNotEmpty) {
|
|
||||||
await _insertCache(db, dbAccount, inserts, null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _cacheRemote(
|
|
||||||
Account account, File dir, List<File> remote) async {
|
|
||||||
final sqlFiles = await remote.convertToFileCompanion(null);
|
|
||||||
await _c.sqliteDb.use((db) async {
|
|
||||||
final dbAccount = await db.accountOf(account);
|
|
||||||
final inserts = await _updateCache(db, dbAccount, sqlFiles, remote, dir);
|
|
||||||
if (inserts.isNotEmpty) {
|
|
||||||
await _insertCache(db, dbAccount, inserts, dir);
|
|
||||||
}
|
|
||||||
if (_dirRowId == null) {
|
|
||||||
_log.severe("[_cacheRemote] Dir not inserted");
|
|
||||||
throw StateError("Row ID for dir is null");
|
|
||||||
}
|
|
||||||
|
|
||||||
final dirFileQuery = db.select(db.dirFiles)
|
|
||||||
..where((t) => t.dir.equals(_dirRowId!))
|
|
||||||
..orderBy([(t) => sql.OrderingTerm.asc(t.child)]);
|
|
||||||
final dirFiles = await dirFileQuery.get();
|
|
||||||
final diff = getDiff(dirFiles.map((e) => e.child),
|
|
||||||
_childRowIds.sorted(Comparable.compare));
|
|
||||||
if (diff.onlyInB.isNotEmpty) {
|
|
||||||
await db.batch((batch) {
|
|
||||||
// insert new children
|
|
||||||
batch.insertAll(db.dirFiles,
|
|
||||||
diff.onlyInB.map((k) => sql.DirFile(dir: _dirRowId!, child: k)));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (diff.onlyInA.isNotEmpty) {
|
|
||||||
// remove entries from the DirFiles table first
|
|
||||||
await diff.onlyInA.withPartitionNoReturn((sublist) async {
|
|
||||||
final deleteQuery = db.delete(db.dirFiles)
|
|
||||||
..where((t) => t.child.isIn(sublist))
|
|
||||||
..where((t) =>
|
|
||||||
t.dir.equals(_dirRowId!) | t.dir.equalsExp(db.dirFiles.child));
|
|
||||||
await deleteQuery.go();
|
|
||||||
}, sql.maxByFileIdsSize);
|
|
||||||
|
|
||||||
// select files having another dir parent under this account (i.e.,
|
|
||||||
// moved files)
|
|
||||||
final moved = await diff.onlyInA.withPartition((sublist) async {
|
|
||||||
final query = db.selectOnly(db.dirFiles).join([
|
|
||||||
sql.innerJoin(db.accountFiles,
|
|
||||||
db.accountFiles.file.equalsExp(db.dirFiles.dir)),
|
|
||||||
]);
|
|
||||||
query
|
|
||||||
..addColumns([db.dirFiles.child])
|
|
||||||
..where(db.accountFiles.account.equals(dbAccount.rowId))
|
|
||||||
..where(db.dirFiles.child.isIn(sublist));
|
|
||||||
return query.map((r) => r.read(db.dirFiles.child)!).get();
|
|
||||||
}, sql.maxByFileIdsSize);
|
|
||||||
final removed = diff.onlyInA.where((e) => !moved.contains(e)).toList();
|
|
||||||
if (removed.isNotEmpty) {
|
|
||||||
// delete obsolete children
|
|
||||||
await _removeSqliteFiles(db, dbAccount, removed);
|
|
||||||
await db.cleanUpDanglingFiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update Db files in [sqlFiles]
|
|
||||||
///
|
|
||||||
/// Return a list of DB files that are not yet inserted to the DB (thus not
|
|
||||||
/// possible to update)
|
|
||||||
Future<List<sql.CompleteFileCompanion>> _updateCache(
|
|
||||||
sql.SqliteDb db,
|
|
||||||
sql.Account dbAccount,
|
|
||||||
Iterable<sql.CompleteFileCompanion> sqlFiles,
|
|
||||||
Iterable<File> remoteFiles,
|
|
||||||
File? dir,
|
|
||||||
) async {
|
|
||||||
// query list of rowIds for files in [remoteFiles]
|
|
||||||
final rowIds = await db.accountFileRowIdsByFileIds(
|
|
||||||
sql.ByAccount.sql(dbAccount), remoteFiles.map((f) => f.fileId!));
|
|
||||||
final rowIdsMap = Map.fromEntries(rowIds.map((e) => MapEntry(e.fileId, e)));
|
|
||||||
|
|
||||||
final inserts = <sql.CompleteFileCompanion>[];
|
|
||||||
// for updates, we use batch to speed up the process
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final f in sqlFiles) {
|
|
||||||
final isSupportedImageFormat =
|
|
||||||
file_util.isSupportedImageMime(f.file.contentType.value ?? "");
|
|
||||||
final thisRowIds = rowIdsMap[f.file.fileId.value];
|
|
||||||
if (thisRowIds != null) {
|
|
||||||
// updates
|
|
||||||
batch.update(
|
|
||||||
db.files,
|
|
||||||
f.file,
|
|
||||||
where: (sql.$FilesTable t) => t.rowId.equals(thisRowIds.fileRowId),
|
|
||||||
);
|
|
||||||
batch.update(
|
|
||||||
db.accountFiles,
|
|
||||||
f.accountFile,
|
|
||||||
where: (sql.$AccountFilesTable t) =>
|
|
||||||
t.rowId.equals(thisRowIds.accountFileRowId),
|
|
||||||
);
|
|
||||||
if (f.image != null) {
|
|
||||||
batch.update(
|
|
||||||
db.images,
|
|
||||||
f.image!,
|
|
||||||
where: (sql.$ImagesTable t) =>
|
|
||||||
t.accountFile.equals(thisRowIds.accountFileRowId),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (isSupportedImageFormat) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.images,
|
|
||||||
(sql.$ImagesTable t) =>
|
|
||||||
t.accountFile.equals(thisRowIds.accountFileRowId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (f.imageLocation != null) {
|
|
||||||
batch.update(
|
|
||||||
db.imageLocations,
|
|
||||||
f.imageLocation!,
|
|
||||||
where: (sql.$ImageLocationsTable t) =>
|
|
||||||
t.accountFile.equals(thisRowIds.accountFileRowId),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
if (isSupportedImageFormat) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.imageLocations,
|
|
||||||
(sql.$ImageLocationsTable t) =>
|
|
||||||
t.accountFile.equals(thisRowIds.accountFileRowId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (f.trash != null) {
|
|
||||||
batch.update(
|
|
||||||
db.trashes,
|
|
||||||
f.trash!,
|
|
||||||
where: (sql.$TrashesTable t) =>
|
|
||||||
t.file.equals(thisRowIds.fileRowId),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.trashes,
|
|
||||||
(sql.$TrashesTable t) => t.file.equals(thisRowIds.fileRowId),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_onRowCached(thisRowIds.fileRowId, f, dir);
|
|
||||||
} else {
|
|
||||||
// inserts, do it later
|
|
||||||
inserts.add(f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
_log.info(
|
|
||||||
"[_updateCache] Updated ${sqlFiles.length - inserts.length} files");
|
|
||||||
return inserts;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _insertCache(sql.SqliteDb db, sql.Account dbAccount,
|
|
||||||
List<sql.CompleteFileCompanion> sqlFiles, File? dir) async {
|
|
||||||
_log.info("[_insertCache] Insert ${sqlFiles.length} files");
|
|
||||||
// check if the files exist in the db in other accounts
|
|
||||||
final entries =
|
|
||||||
await sqlFiles.map((f) => f.file.fileId.value).withPartition((sublist) {
|
|
||||||
final query = db.queryFiles().run((q) {
|
|
||||||
q
|
|
||||||
..setQueryMode(
|
|
||||||
sql.FilesQueryMode.expression,
|
|
||||||
expressions: [db.files.rowId, db.files.fileId],
|
|
||||||
)
|
|
||||||
..setAccountless()
|
|
||||||
..byServerRowId(dbAccount.server)
|
|
||||||
..byFileIds(sublist);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) =>
|
|
||||||
MapEntry(r.read(db.files.fileId)!, r.read(db.files.rowId)!))
|
|
||||||
.get();
|
|
||||||
}, sql.maxByFileIdsSize);
|
|
||||||
final fileRowIdMap = Map.fromEntries(entries);
|
|
||||||
|
|
||||||
await Future.wait(sqlFiles.map((f) async {
|
|
||||||
var rowId = fileRowIdMap[f.file.fileId.value];
|
|
||||||
if (rowId != null) {
|
|
||||||
// shared file that exists in other accounts
|
|
||||||
} else {
|
|
||||||
final dbFile = await db.into(db.files).insertReturning(
|
|
||||||
f.file.copyWith(server: sql.Value(dbAccount.server)),
|
|
||||||
);
|
|
||||||
rowId = dbFile.rowId;
|
|
||||||
}
|
|
||||||
final dbAccountFile =
|
|
||||||
await db.into(db.accountFiles).insertReturning(f.accountFile.copyWith(
|
|
||||||
account: sql.Value(dbAccount.rowId),
|
|
||||||
file: sql.Value(rowId),
|
|
||||||
));
|
|
||||||
if (f.image != null) {
|
|
||||||
await db.into(db.images).insert(
|
|
||||||
f.image!.copyWith(accountFile: sql.Value(dbAccountFile.rowId)));
|
|
||||||
}
|
|
||||||
if (f.imageLocation != null) {
|
|
||||||
await db.into(db.imageLocations).insert(f.imageLocation!
|
|
||||||
.copyWith(accountFile: sql.Value(dbAccountFile.rowId)));
|
|
||||||
}
|
|
||||||
if (f.trash != null) {
|
|
||||||
await db
|
|
||||||
.into(db.trashes)
|
|
||||||
.insert(f.trash!.copyWith(file: sql.Value(rowId)));
|
|
||||||
}
|
|
||||||
_onRowCached(rowId, f, dir);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onRowCached(int rowId, sql.CompleteFileCompanion dbFile, File? dir) {
|
|
||||||
if (dir != null) {
|
|
||||||
if (_compareIdentity(dbFile, dir)) {
|
|
||||||
_dirRowId = rowId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_childRowIds.add(rowId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _compareIdentity(sql.CompleteFileCompanion dbFile, File appFile) {
|
|
||||||
if (appFile.fileId != null) {
|
|
||||||
return appFile.fileId == dbFile.file.fileId.value;
|
|
||||||
} else {
|
|
||||||
return appFile.strippedPathWithEmpty ==
|
|
||||||
dbFile.accountFile.relativePath.value;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
||||||
int? _dirRowId;
|
|
||||||
final _childRowIds = <int>[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileSqliteCacheRemover {
|
class FileSqliteCacheRemover {
|
||||||
FileSqliteCacheRemover(this._c) : assert(require(_c));
|
const FileSqliteCacheRemover(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// Remove a file/dir from cache
|
/// Remove a file/dir from cache
|
||||||
Future<void> call(Account account, FileDescriptor f) async {
|
Future<void> call(Account account, FileDescriptor f) async {
|
||||||
await _c.sqliteDb.use((db) async {
|
await _c.npDb.deleteFile(
|
||||||
final dbAccount = await db.accountOf(account);
|
account: account.toDb(),
|
||||||
final rowIds = await db.accountFileRowIdsOf(f, sqlAccount: dbAccount);
|
file: f.toDbKey(),
|
||||||
await _removeSqliteFiles(db, dbAccount, [rowIds.fileRowId]);
|
);
|
||||||
await db.cleanUpDanglingFiles();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileSqliteCacheEmptier {
|
class FileSqliteCacheEmptier {
|
||||||
FileSqliteCacheEmptier(this._c) : assert(require(_c));
|
const FileSqliteCacheEmptier(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// Empty a dir from cache
|
/// Empty a dir from cache
|
||||||
Future<void> call(Account account, File dir) async {
|
Future<void> call(Account account, File dir) async {
|
||||||
await _c.sqliteDb.use((db) async {
|
await _c.npDb.truncateDir(
|
||||||
final dbAccount = await db.accountOf(account);
|
account: account.toDb(),
|
||||||
final rowIds = await db.accountFileRowIdsOf(dir, sqlAccount: dbAccount);
|
dir: dir.toDbKey(),
|
||||||
|
);
|
||||||
// remove children
|
|
||||||
final childIdsQuery = db.selectOnly(db.dirFiles)
|
|
||||||
..addColumns([db.dirFiles.child])
|
|
||||||
..where(db.dirFiles.dir.equals(rowIds.fileRowId));
|
|
||||||
final childIds =
|
|
||||||
await childIdsQuery.map((r) => r.read(db.dirFiles.child)!).get();
|
|
||||||
childIds.removeWhere((id) => id == rowIds.fileRowId);
|
|
||||||
if (childIds.isNotEmpty) {
|
|
||||||
await _removeSqliteFiles(db, dbAccount, childIds);
|
|
||||||
await db.cleanUpDanglingFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove dir in DirFiles
|
|
||||||
await (db.delete(db.dirFiles)
|
|
||||||
..where((t) => t.dir.equals(rowIds.fileRowId)))
|
|
||||||
.go();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a files from the cache db
|
|
||||||
///
|
|
||||||
/// If a file is a dir, its children will also be recursively removed
|
|
||||||
Future<void> _removeSqliteFiles(
|
|
||||||
sql.SqliteDb db, sql.Account dbAccount, List<int> fileRowIds) async {
|
|
||||||
// query list of children, in case some of the files are dirs
|
|
||||||
final childRowIds = await fileRowIds.withPartition((sublist) {
|
|
||||||
final childQuery = db.selectOnly(db.dirFiles)
|
|
||||||
..addColumns([db.dirFiles.child])
|
|
||||||
..where(db.dirFiles.dir.isIn(sublist));
|
|
||||||
return childQuery.map((r) => r.read(db.dirFiles.child)!).get();
|
|
||||||
}, sql.maxByFileIdsSize);
|
|
||||||
childRowIds.removeWhere((id) => fileRowIds.contains(id));
|
|
||||||
|
|
||||||
// remove the files in AccountFiles table. We are not removing in Files table
|
|
||||||
// because a file could be associated with multiple accounts
|
|
||||||
await fileRowIds.withPartitionNoReturn((sublist) async {
|
|
||||||
await (db.delete(db.accountFiles)
|
|
||||||
..where(
|
|
||||||
(t) => t.account.equals(dbAccount.rowId) & t.file.isIn(sublist)))
|
|
||||||
.go();
|
|
||||||
}, sql.maxByFileIdsSize);
|
|
||||||
|
|
||||||
if (childRowIds.isNotEmpty) {
|
|
||||||
// remove children recursively
|
|
||||||
return _removeSqliteFiles(db, dbAccount, childRowIds);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/entity_converter.dart';
|
import 'package:nc_photos/api/entity_converter.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/nc_album.dart';
|
import 'package:nc_photos/entity/nc_album.dart';
|
||||||
import 'package:nc_photos/entity/nc_album/repo.dart';
|
import 'package:nc_photos/entity/nc_album/repo.dart';
|
||||||
import 'package:nc_photos/entity/nc_album_item.dart';
|
import 'package:nc_photos/entity/nc_album_item.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:np_api/np_api.dart' as api;
|
import 'package:np_api/np_api.dart' as api;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
|
||||||
|
@ -152,18 +150,16 @@ class NcAlbumRemoteDataSource implements NcAlbumDataSource {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource {
|
class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource {
|
||||||
const NcAlbumSqliteDbDataSource(this.sqliteDb);
|
const NcAlbumSqliteDbDataSource(this.npDb);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<NcAlbum>> getAlbums(Account account) async {
|
Future<List<NcAlbum>> getAlbums(Account account) async {
|
||||||
_log.info("[getAlbums] account: ${account.userId}");
|
_log.info("[getAlbums] account: ${account.userId}");
|
||||||
final dbAlbums = await sqliteDb.use((db) async {
|
final results = await npDb.getNcAlbums(account: account.toDb());
|
||||||
return await db.ncAlbumsByAccount(account: sql.ByAccount.app(account));
|
return results
|
||||||
});
|
.map((e) {
|
||||||
return dbAlbums
|
|
||||||
.map((a) {
|
|
||||||
try {
|
try {
|
||||||
return SqliteNcAlbumConverter.fromSql(account.userId.toString(), a);
|
return DbNcAlbumConverter.fromDb(account.userId.toString(), e);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe(
|
_log.severe(
|
||||||
"[getAlbums] Failed while converting DB entry", e, stackTrace);
|
"[getAlbums] Failed while converting DB entry", e, stackTrace);
|
||||||
|
@ -177,40 +173,28 @@ class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource {
|
||||||
@override
|
@override
|
||||||
Future<void> create(Account account, NcAlbum album) async {
|
Future<void> create(Account account, NcAlbum album) async {
|
||||||
_log.info("[create] account: ${account.userId}, album: ${album.path}");
|
_log.info("[create] account: ${account.userId}, album: ${album.path}");
|
||||||
await sqliteDb.use((db) async {
|
await npDb.addNcAlbum(account: account.toDb(), album: album.toDb());
|
||||||
await db.insertNcAlbum(
|
|
||||||
account: sql.ByAccount.app(account),
|
|
||||||
object: SqliteNcAlbumConverter.toSql(null, album),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> remove(Account account, NcAlbum album) async {
|
Future<void> remove(Account account, NcAlbum album) async {
|
||||||
_log.info("[remove] account: ${account.userId}, album: ${album.path}");
|
_log.info("[remove] account: ${account.userId}, album: ${album.path}");
|
||||||
await sqliteDb.use((db) async {
|
await npDb.deleteNcAlbum(account: account.toDb(), album: album.toDb());
|
||||||
await db.deleteNcAlbumByRelativePath(
|
|
||||||
account: sql.ByAccount.app(account),
|
|
||||||
relativePath: album.strippedPath,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<NcAlbumItem>> getItems(Account account, NcAlbum album) async {
|
Future<List<NcAlbumItem>> getItems(Account account, NcAlbum album) async {
|
||||||
_log.info(
|
_log.info(
|
||||||
"[getItems] account: ${account.userId}, album: ${album.strippedPath}");
|
"[getItems] account: ${account.userId}, album: ${album.strippedPath}");
|
||||||
final dbItems = await sqliteDb.use((db) async {
|
final results = await npDb.getNcAlbumItemsByParent(
|
||||||
return await db.ncAlbumItemsByParentRelativePath(
|
account: account.toDb(),
|
||||||
account: sql.ByAccount.app(account),
|
parent: album.toDb(),
|
||||||
parentRelativePath: album.strippedPath,
|
);
|
||||||
);
|
return results
|
||||||
});
|
.map((e) {
|
||||||
return dbItems
|
|
||||||
.map((i) {
|
|
||||||
try {
|
try {
|
||||||
return SqliteNcAlbumItemConverter.fromSql(account.userId.toString(),
|
return DbNcAlbumItemConverter.fromDb(account.userId.toString(),
|
||||||
album.strippedPath, album.isOwned, i);
|
album.strippedPath, album.isOwned, e);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe(
|
_log.severe(
|
||||||
"[getItems] Failed while converting DB entry", e, stackTrace);
|
"[getItems] Failed while converting DB entry", e, stackTrace);
|
||||||
|
@ -223,83 +207,25 @@ class NcAlbumSqliteDbDataSource implements NcAlbumCacheDataSource {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateAlbumsCache(Account account, List<NcAlbum> remote) async {
|
Future<void> updateAlbumsCache(Account account, List<NcAlbum> remote) async {
|
||||||
await sqliteDb.use((db) async {
|
_log.info(
|
||||||
final dbAccount = await db.accountOf(account);
|
"[updateAlbumsCache] account: ${account.userId}, remote: ${remote.map((e) => e.strippedPath)}");
|
||||||
final existings = (await db.partialNcAlbumsByAccount(
|
await npDb.syncNcAlbums(
|
||||||
account: sql.ByAccount.sql(dbAccount),
|
account: account.toDb(),
|
||||||
columns: [db.ncAlbums.rowId, db.ncAlbums.relativePath],
|
albums: remote.map(DbNcAlbumConverter.toDb).toList(),
|
||||||
))
|
);
|
||||||
.whereNotNull()
|
|
||||||
.toList();
|
|
||||||
await db.batch((batch) async {
|
|
||||||
for (final r in remote) {
|
|
||||||
final dbObj = SqliteNcAlbumConverter.toSql(dbAccount, r);
|
|
||||||
final found = existings.indexWhere((e) => e[1] == r.strippedPath);
|
|
||||||
if (found != -1) {
|
|
||||||
// existing record, update it
|
|
||||||
batch.update(
|
|
||||||
db.ncAlbums,
|
|
||||||
dbObj,
|
|
||||||
where: (sql.$NcAlbumsTable t) =>
|
|
||||||
t.rowId.equals(existings[found][0]),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// insert
|
|
||||||
batch.insert(db.ncAlbums, dbObj);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (final e in existings
|
|
||||||
.where((e) => !remote.any((r) => r.strippedPath == e[1]))) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.ncAlbums,
|
|
||||||
(sql.$NcAlbumsTable t) => t.rowId.equals(e[0]),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> updateItemsCache(
|
Future<void> updateItemsCache(
|
||||||
Account account, NcAlbum album, List<NcAlbumItem> remote) async {
|
Account account, NcAlbum album, List<NcAlbumItem> remote) async {
|
||||||
await sqliteDb.use((db) async {
|
_log.info(
|
||||||
final dbAlbum = await db.ncAlbumByRelativePath(
|
"[updateItemsCache] account: ${account.userId}, album: ${album.name}, remote: ${remote.map((e) => e.strippedPath)}");
|
||||||
account: sql.ByAccount.app(account),
|
await npDb.syncNcAlbumItems(
|
||||||
relativePath: album.strippedPath,
|
account: account.toDb(),
|
||||||
);
|
album: album.toDb(),
|
||||||
final existingItems = await db.ncAlbumItemsByParent(
|
items: remote.map(DbNcAlbumItemConverter.toDb).toList(),
|
||||||
parent: dbAlbum!,
|
);
|
||||||
);
|
|
||||||
final diff = getDiffWith<NcAlbumItem>(
|
|
||||||
existingItems
|
|
||||||
.map((e) => SqliteNcAlbumItemConverter.fromSql(
|
|
||||||
account.userId.raw, album.strippedPath, album.isOwned, e))
|
|
||||||
.sorted(NcAlbumItemExtension.identityComparator),
|
|
||||||
remote.sorted(NcAlbumItemExtension.identityComparator),
|
|
||||||
NcAlbumItemExtension.identityComparator,
|
|
||||||
);
|
|
||||||
if (diff.onlyInA.isNotEmpty || diff.onlyInB.isNotEmpty) {
|
|
||||||
await db.batch((batch) async {
|
|
||||||
for (final item in diff.onlyInB) {
|
|
||||||
// new
|
|
||||||
batch.insert(
|
|
||||||
db.ncAlbumItems,
|
|
||||||
SqliteNcAlbumItemConverter.toSql(dbAlbum, item),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final rmIds = diff.onlyInA.map((e) => e.fileId).toList();
|
|
||||||
if (rmIds.isNotEmpty) {
|
|
||||||
// removed
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.ncAlbumItems,
|
|
||||||
(sql.$NcAlbumItemsTable t) =>
|
|
||||||
t.parent.equals(dbAlbum.rowId) & t.fileId.isIn(rmIds),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final sql.SqliteDb sqliteDb;
|
final NpDb npDb;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,9 +64,6 @@ extension NcAlbumItemExtension on NcAlbumItem {
|
||||||
|
|
||||||
int get identityHashCode => fileId.hashCode;
|
int get identityHashCode => fileId.hashCode;
|
||||||
|
|
||||||
static int identityComparator(NcAlbumItem a, NcAlbumItem b) =>
|
|
||||||
a.fileId.compareTo(b.fileId);
|
|
||||||
|
|
||||||
File toFile() {
|
File toFile() {
|
||||||
Metadata? metadata;
|
Metadata? metadata;
|
||||||
if (fileMetadataWidth != null && fileMetadataHeight != null) {
|
if (fileMetadataWidth != null && fileMetadataHeight != null) {
|
||||||
|
|
|
@ -2,18 +2,17 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/entity_converter.dart';
|
import 'package:nc_photos/api/entity_converter.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/recognize_face.dart';
|
import 'package:nc_photos/entity/recognize_face.dart';
|
||||||
import 'package:nc_photos/entity/recognize_face/repo.dart';
|
import 'package:nc_photos/entity/recognize_face/repo.dart';
|
||||||
import 'package:nc_photos/entity/recognize_face_item.dart';
|
import 'package:nc_photos/entity/recognize_face_item.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/table.dart';
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:np_api/np_api.dart' as api;
|
import 'package:np_api/np_api.dart' as api;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
|
||||||
|
@ -110,20 +109,16 @@ class RecognizeFaceRemoteDataSource implements RecognizeFaceDataSource {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class RecognizeFaceSqliteDbDataSource implements RecognizeFaceDataSource {
|
class RecognizeFaceSqliteDbDataSource implements RecognizeFaceDataSource {
|
||||||
const RecognizeFaceSqliteDbDataSource(this.sqliteDb);
|
const RecognizeFaceSqliteDbDataSource(this.db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<RecognizeFace>> getFaces(Account account) async {
|
Future<List<RecognizeFace>> getFaces(Account account) async {
|
||||||
_log.info("[getFaces] $account");
|
_log.info("[getFaces] $account");
|
||||||
final dbFaces = await sqliteDb.use((db) async {
|
final results = await db.getRecognizeFaces(account: account.toDb());
|
||||||
return await db.allRecognizeFaces(
|
return results
|
||||||
account: sql.ByAccount.app(account),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return dbFaces
|
|
||||||
.map((f) {
|
.map((f) {
|
||||||
try {
|
try {
|
||||||
return SqliteRecognizeFaceConverter.fromSql(f);
|
return DbRecognizeFaceConverter.fromDb(f);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe(
|
_log.severe(
|
||||||
"[getFaces] Failed while converting DB entry", e, stackTrace);
|
"[getFaces] Failed while converting DB entry", e, stackTrace);
|
||||||
|
@ -138,8 +133,23 @@ class RecognizeFaceSqliteDbDataSource implements RecognizeFaceDataSource {
|
||||||
Future<List<RecognizeFaceItem>> getItems(
|
Future<List<RecognizeFaceItem>> getItems(
|
||||||
Account account, RecognizeFace face) async {
|
Account account, RecognizeFace face) async {
|
||||||
_log.info("[getItems] $face");
|
_log.info("[getItems] $face");
|
||||||
final results = await getMultiFaceItems(account, [face]);
|
final results = await db.getRecognizeFaceItemsByFaceLabel(
|
||||||
return results[face]!;
|
account: account.toDb(),
|
||||||
|
label: face.label,
|
||||||
|
);
|
||||||
|
return results
|
||||||
|
.map((r) {
|
||||||
|
try {
|
||||||
|
return DbRecognizeFaceItemConverter.fromDb(
|
||||||
|
account.userId.toString(), face.label, r);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe(
|
||||||
|
"[getItems] Failed while converting DB entry", e, stackTrace);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.whereNotNull()
|
||||||
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -147,42 +157,25 @@ class RecognizeFaceSqliteDbDataSource implements RecognizeFaceDataSource {
|
||||||
Account account,
|
Account account,
|
||||||
List<RecognizeFace> faces, {
|
List<RecognizeFace> faces, {
|
||||||
ErrorWithValueHandler<RecognizeFace>? onError,
|
ErrorWithValueHandler<RecognizeFace>? onError,
|
||||||
List<RecognizeFaceItemSort>? orderBy,
|
|
||||||
int? limit,
|
|
||||||
}) async {
|
}) async {
|
||||||
_log.info("[getMultiFaceItems] ${faces.toReadableString()}");
|
_log.info("[getMultiFaceItems] ${faces.toReadableString()}");
|
||||||
final dbItems = await sqliteDb.use((db) async {
|
final results = await db.getRecognizeFaceItemsByFaceLabels(
|
||||||
final results = await Future.wait(faces.map((f) async {
|
account: account.toDb(),
|
||||||
try {
|
labels: faces.map((e) => e.label).toList(),
|
||||||
return MapEntry(
|
);
|
||||||
f,
|
return results.entries
|
||||||
await db.recognizeFaceItemsByParentLabel(
|
.map((e) {
|
||||||
account: sql.ByAccount.app(account),
|
|
||||||
label: f.label,
|
|
||||||
orderBy: orderBy?.toOrderingItem(db).toList(),
|
|
||||||
limit: limit,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
onError?.call(f, e, stackTrace);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
return results.whereNotNull().toMap();
|
|
||||||
});
|
|
||||||
return dbItems.entries
|
|
||||||
.map((entry) {
|
|
||||||
final face = entry.key;
|
|
||||||
try {
|
try {
|
||||||
return MapEntry(
|
return MapEntry(
|
||||||
face,
|
faces.firstWhere((f) => f.label == e.key),
|
||||||
entry.value
|
e.value
|
||||||
.map((i) => SqliteRecognizeFaceItemConverter.fromSql(
|
.map((f) => DbRecognizeFaceItemConverter.fromDb(
|
||||||
account.userId.raw, face.label, i))
|
account.userId.toString(), e.key, f))
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
onError?.call(face, e, stackTrace);
|
_log.severe("[getMultiFaceItems] Failed while converting DB entry",
|
||||||
|
e, stackTrace);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -196,16 +189,30 @@ class RecognizeFaceSqliteDbDataSource implements RecognizeFaceDataSource {
|
||||||
List<RecognizeFace> faces, {
|
List<RecognizeFace> faces, {
|
||||||
ErrorWithValueHandler<RecognizeFace>? onError,
|
ErrorWithValueHandler<RecognizeFace>? onError,
|
||||||
}) async {
|
}) async {
|
||||||
final results = await getMultiFaceItems(
|
_log.info("[getMultiFaceLastItems] ${faces.toReadableString()}");
|
||||||
account,
|
final results = await db.getLatestRecognizeFaceItemsByFaceLabels(
|
||||||
faces,
|
account: account.toDb(),
|
||||||
onError: onError,
|
labels: faces.map((e) => e.label).toList(),
|
||||||
orderBy: [RecognizeFaceItemSort.fileIdDesc],
|
|
||||||
limit: 1,
|
|
||||||
);
|
);
|
||||||
return (results..removeWhere((key, value) => value.isEmpty))
|
return results.entries
|
||||||
.map((key, value) => MapEntry(key, value.first));
|
.map((e) {
|
||||||
|
try {
|
||||||
|
return MapEntry(
|
||||||
|
faces.firstWhere((f) => f.label == e.key),
|
||||||
|
DbRecognizeFaceItemConverter.fromDb(
|
||||||
|
account.userId.toString(), e.key, e.value),
|
||||||
|
);
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe(
|
||||||
|
"[getMultiFaceLastItems] Failed while converting DB entry",
|
||||||
|
e,
|
||||||
|
stackTrace);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.whereNotNull()
|
||||||
|
.toMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
final sql.SqliteDb sqliteDb;
|
final NpDb db;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
|
import 'package:nc_photos/entity/file_descriptor.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/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ class SearchCriteria {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class SearchFilter {
|
abstract class SearchFilter {
|
||||||
void apply(sql.FilesQueryBuilder query);
|
Map<Symbol, Object> toQueryArgument();
|
||||||
bool isSatisfy(File file);
|
bool isSatisfy(File file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,25 +38,17 @@ enum SearchFileType {
|
||||||
video,
|
video,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension on SearchFileType {
|
|
||||||
String toSqlPattern() {
|
|
||||||
switch (this) {
|
|
||||||
case SearchFileType.image:
|
|
||||||
return "image/%";
|
|
||||||
|
|
||||||
case SearchFileType.video:
|
|
||||||
return "video/%";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@toString
|
@toString
|
||||||
class SearchFileTypeFilter implements SearchFilter {
|
class SearchFileTypeFilter implements SearchFilter {
|
||||||
const SearchFileTypeFilter(this.type);
|
const SearchFileTypeFilter(this.type);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
apply(sql.FilesQueryBuilder query) {
|
Map<Symbol, Object> toQueryArgument() {
|
||||||
query.byMimePattern(type.toSqlPattern());
|
if (type == SearchFileType.image) {
|
||||||
|
return {#mimes: file_util.supportedImageFormatMimes};
|
||||||
|
} else {
|
||||||
|
return {#mimes: file_util.supportedVideoFormatMimes};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -81,8 +73,8 @@ class SearchFavoriteFilter implements SearchFilter {
|
||||||
const SearchFavoriteFilter(this.value);
|
const SearchFavoriteFilter(this.value);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
apply(sql.FilesQueryBuilder query) {
|
Map<Symbol, Object> toQueryArgument() {
|
||||||
query.byFavorite(value);
|
return {#isFavorite: value};
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -97,7 +89,7 @@ class SearchFavoriteFilter implements SearchFilter {
|
||||||
class SearchRepo {
|
class SearchRepo {
|
||||||
const SearchRepo(this.dataSrc);
|
const SearchRepo(this.dataSrc);
|
||||||
|
|
||||||
Future<List<File>> list(Account account, SearchCriteria criteria) =>
|
Future<List<FileDescriptor>> list(Account account, SearchCriteria criteria) =>
|
||||||
dataSrc.list(account, criteria);
|
dataSrc.list(account, criteria);
|
||||||
|
|
||||||
final SearchDataSource dataSrc;
|
final SearchDataSource dataSrc;
|
||||||
|
@ -105,5 +97,5 @@ class SearchRepo {
|
||||||
|
|
||||||
abstract class SearchDataSource {
|
abstract class SearchDataSource {
|
||||||
/// List all results from a given search criteria
|
/// List all results from a given search criteria
|
||||||
Future<List<File>> list(Account account, SearchCriteria criteria);
|
Future<List<FileDescriptor>> list(Account account, SearchCriteria criteria);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,30 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.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_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/person/builder.dart';
|
import 'package:nc_photos/entity/person/builder.dart';
|
||||||
import 'package:nc_photos/entity/search.dart';
|
import 'package:nc_photos/entity/search.dart';
|
||||||
import 'package:nc_photos/entity/search_util.dart' as search_util;
|
import 'package:nc_photos/entity/search_util.dart' as search_util;
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/list_tagged_file.dart';
|
import 'package:nc_photos/use_case/list_tagged_file.dart';
|
||||||
import 'package:nc_photos/use_case/person/list_person_face.dart';
|
import 'package:nc_photos/use_case/person/list_person_face.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class SearchSqliteDbDataSource implements SearchDataSource {
|
class SearchSqliteDbDataSource implements SearchDataSource {
|
||||||
SearchSqliteDbDataSource(this._c);
|
const SearchSqliteDbDataSource(this._c);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
list(Account account, SearchCriteria criteria) async {
|
Future<List<FileDescriptor>> list(
|
||||||
|
Account account, SearchCriteria criteria) async {
|
||||||
_log.info("[list] $criteria");
|
_log.info("[list] $criteria");
|
||||||
final stopwatch = Stopwatch()..start();
|
final stopwatch = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
|
@ -50,81 +49,57 @@ class SearchSqliteDbDataSource implements SearchDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<File>> _listByPath(
|
Future<List<FileDescriptor>> _listByPath(
|
||||||
Account account, SearchCriteria criteria, Set<String> keywords) async {
|
Account account, SearchCriteria criteria, Set<String> keywords) async {
|
||||||
try {
|
try {
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final args = {
|
||||||
final query = db.queryFiles().run((q) {
|
#account: account.toDb(),
|
||||||
q.setQueryMode(sql.FilesQueryMode.completeFile);
|
#includeRelativeRoots: account.roots,
|
||||||
q.setAppAccount(account);
|
#excludeRelativeRoots: [
|
||||||
for (final r in account.roots) {
|
remote_storage_util.remoteStorageDirRelativePath
|
||||||
if (r.isNotEmpty) {
|
],
|
||||||
q.byOrRelativePathPattern("$r/%");
|
#relativePathKeywords: keywords.toList(),
|
||||||
}
|
#mimes: file_util.supportedFormatMimes,
|
||||||
}
|
};
|
||||||
for (final f in criteria.filters) {
|
for (final f in criteria.filters) {
|
||||||
f.apply(q);
|
args.addAll(f.toQueryArgument());
|
||||||
}
|
}
|
||||||
return q.build();
|
final List<DbFileDescriptor> dbFiles =
|
||||||
});
|
await Function.apply(_c.npDb.getFileDescriptors, null, args);
|
||||||
// limit to supported formats only
|
return dbFiles
|
||||||
query.where(db.files.contentType.like("image/%") |
|
.map((e) => DbFileDescriptorConverter.fromDb(
|
||||||
db.files.contentType.like("video/%"));
|
account.userId.toCaseInsensitiveString(), e))
|
||||||
for (final k in keywords) {
|
.toList();
|
||||||
query.where(db.accountFiles.relativePath.like("%$k%"));
|
|
||||||
}
|
|
||||||
return await query
|
|
||||||
.map((r) => sql.CompleteFile(
|
|
||||||
r.readTable(db.files),
|
|
||||||
r.readTable(db.accountFiles),
|
|
||||||
r.readTableOrNull(db.images),
|
|
||||||
r.readTableOrNull(db.imageLocations),
|
|
||||||
r.readTableOrNull(db.trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
return await dbFiles.convertToAppFile(account);
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe("[_listByPath] Failed while _listByPath", e, stackTrace);
|
_log.severe("[_listByPath] Failed while _listByPath", e, stackTrace);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<File>> _listByLocation(
|
Future<List<FileDescriptor>> _listByLocation(
|
||||||
Account account, SearchCriteria criteria) async {
|
Account account, SearchCriteria criteria) async {
|
||||||
// location search requires exact match, for example, searching "united"
|
// location search requires exact match, for example, searching "united"
|
||||||
// will NOT return results from US, UK, UAE, etc. Searching by the alpha2
|
// will NOT return results from US, UK, UAE, etc. Searching by the alpha2
|
||||||
// code is supported
|
// code is supported
|
||||||
try {
|
try {
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final args = {
|
||||||
final query = db.queryFiles().run((q) {
|
#account: account.toDb(),
|
||||||
q.setQueryMode(sql.FilesQueryMode.completeFile);
|
#includeRelativeRoots: account.roots,
|
||||||
q.setAppAccount(account);
|
#excludeRelativeRoots: [
|
||||||
for (final r in account.roots) {
|
remote_storage_util.remoteStorageDirRelativePath
|
||||||
if (r.isNotEmpty) {
|
],
|
||||||
q.byOrRelativePathPattern("$r/%");
|
#location: criteria.input,
|
||||||
}
|
#mimes: file_util.supportedFormatMimes,
|
||||||
}
|
};
|
||||||
for (final f in criteria.filters) {
|
for (final f in criteria.filters) {
|
||||||
f.apply(q);
|
args.addAll(f.toQueryArgument());
|
||||||
}
|
}
|
||||||
q.byLocation(criteria.input);
|
final List<DbFileDescriptor> dbFiles =
|
||||||
return q.build();
|
await Function.apply(_c.npDb.getFileDescriptors, null, args);
|
||||||
});
|
return dbFiles
|
||||||
// limit to supported formats only
|
.map((e) => DbFileDescriptorConverter.fromDb(
|
||||||
query.where(db.files.contentType.like("image/%") |
|
account.userId.toCaseInsensitiveString(), e))
|
||||||
db.files.contentType.like("video/%"));
|
.toList();
|
||||||
return await query
|
|
||||||
.map((r) => sql.CompleteFile(
|
|
||||||
r.readTable(db.files),
|
|
||||||
r.readTable(db.accountFiles),
|
|
||||||
r.readTableOrNull(db.images),
|
|
||||||
r.readTableOrNull(db.imageLocations),
|
|
||||||
r.readTableOrNull(db.trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
return await dbFiles.convertToAppFile(account);
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.severe(
|
_log.severe(
|
||||||
"[_listByLocation] Failed while _listByLocation", e, stackTrace);
|
"[_listByLocation] Failed while _listByLocation", e, stackTrace);
|
||||||
|
@ -132,21 +107,19 @@ class SearchSqliteDbDataSource implements SearchDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<File>> _listByTag(
|
Future<List<FileDescriptor>> _listByTag(
|
||||||
Account account, SearchCriteria criteria) async {
|
Account account, SearchCriteria criteria) async {
|
||||||
// tag search requires exact match, for example, searching "super" will NOT
|
// tag search requires exact match, for example, searching "super" will NOT
|
||||||
// return results from "super tag"
|
// return results from "super tag"
|
||||||
try {
|
try {
|
||||||
final dbTag = await _c.sqliteDb.use((db) async {
|
final dbTag = await _c.npDb.getTagByDisplayName(
|
||||||
return await db.tagByDisplayName(
|
account: account.toDb(),
|
||||||
appAccount: account,
|
displayName: criteria.input,
|
||||||
displayName: criteria.input,
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
if (dbTag == null) {
|
if (dbTag == null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
final tag = SqliteTagConverter.fromSql(dbTag);
|
final tag = DbTagConverter.fromDb(dbTag);
|
||||||
_log.info("[_listByTag] Found tag: ${tag.displayName}");
|
_log.info("[_listByTag] Found tag: ${tag.displayName}");
|
||||||
final files = await ListTaggedFile(_c)(account, [tag]);
|
final files = await ListTaggedFile(_c)(account, [tag]);
|
||||||
return files
|
return files
|
||||||
|
@ -158,21 +131,20 @@ class SearchSqliteDbDataSource implements SearchDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<File>> _listByPerson(
|
Future<List<FileDescriptor>> _listByPerson(
|
||||||
Account account, SearchCriteria criteria) async {
|
Account account, SearchCriteria criteria) async {
|
||||||
// person search requires exact match of any parts, for example, searching
|
// person search requires exact match of any parts, for example, searching
|
||||||
// "Ada" will return results from "Ada Crook" but NOT "Adabelle"
|
// "Ada" will return results from "Ada Crook" but NOT "Adabelle"
|
||||||
try {
|
try {
|
||||||
final dbPersons = await _c.sqliteDb.use((db) async {
|
final dbPersons = await _c.npDb.searchFaceRecognitionPersonsByName(
|
||||||
return await db.faceRecognitionPersonsByName(
|
account: account.toDb(),
|
||||||
appAccount: account,
|
name: criteria.input,
|
||||||
name: criteria.input,
|
);
|
||||||
);
|
|
||||||
});
|
|
||||||
if (dbPersons.isEmpty) {
|
if (dbPersons.isEmpty) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
final persons = (await dbPersons.convertToAppFaceRecognitionPerson())
|
final persons = dbPersons
|
||||||
|
.map(DbFaceRecognitionPersonConverter.fromDb)
|
||||||
.map((p) => PersonBuilder.byFaceRecognitionPerson(account, p))
|
.map((p) => PersonBuilder.byFaceRecognitionPerson(account, p))
|
||||||
.toList();
|
.toList();
|
||||||
_log.info(
|
_log.info(
|
||||||
|
|
|
@ -1,137 +0,0 @@
|
||||||
part of '../database.dart';
|
|
||||||
|
|
||||||
extension SqliteDbNcAlbumExtension on SqliteDb {
|
|
||||||
Future<List<NcAlbum>> ncAlbumsByAccount({
|
|
||||||
required ByAccount account,
|
|
||||||
}) {
|
|
||||||
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
final query = select(ncAlbums)
|
|
||||||
..where((t) => t.account.equals(account.sqlAccount!.rowId));
|
|
||||||
return query.get();
|
|
||||||
} else {
|
|
||||||
final query = select(ncAlbums).join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(ncAlbums.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
|
||||||
return query.map((r) => r.readTable(ncAlbums)).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<List>> partialNcAlbumsByAccount({
|
|
||||||
required ByAccount account,
|
|
||||||
required List<Expression> columns,
|
|
||||||
}) {
|
|
||||||
final query = selectOnly(ncAlbums)..addColumns(columns);
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
query.where(ncAlbums.account.equals(account.sqlAccount!.rowId));
|
|
||||||
} else {
|
|
||||||
query.join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(ncAlbums.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
|
||||||
}
|
|
||||||
return query.map((r) => columns.map((c) => r.read(c)).toList()).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<NcAlbum?> ncAlbumByRelativePath({
|
|
||||||
required ByAccount account,
|
|
||||||
required String relativePath,
|
|
||||||
}) {
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
final query = select(ncAlbums)
|
|
||||||
..where((t) => t.account.equals(account.sqlAccount!.rowId))
|
|
||||||
..where((t) => t.relativePath.equals(relativePath));
|
|
||||||
return query.getSingleOrNull();
|
|
||||||
} else {
|
|
||||||
final query = select(ncAlbums).join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(ncAlbums.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()))
|
|
||||||
..where(ncAlbums.relativePath.equals(relativePath));
|
|
||||||
return query.map((r) => r.readTable(ncAlbums)).getSingleOrNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> insertNcAlbum({
|
|
||||||
required ByAccount account,
|
|
||||||
required NcAlbumsCompanion object,
|
|
||||||
}) async {
|
|
||||||
final Account dbAccount;
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
dbAccount = account.sqlAccount!;
|
|
||||||
} else {
|
|
||||||
dbAccount = await accountOf(account.appAccount!);
|
|
||||||
}
|
|
||||||
await into(ncAlbums).insert(object.copyWith(
|
|
||||||
account: Value(dbAccount.rowId),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete [NaAlbum] by relativePath
|
|
||||||
///
|
|
||||||
/// Return the number of deleted rows
|
|
||||||
Future<int> deleteNcAlbumByRelativePath({
|
|
||||||
required ByAccount account,
|
|
||||||
required String relativePath,
|
|
||||||
}) async {
|
|
||||||
final Account dbAccount;
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
dbAccount = account.sqlAccount!;
|
|
||||||
} else {
|
|
||||||
dbAccount = await accountOf(account.appAccount!);
|
|
||||||
}
|
|
||||||
return await (delete(ncAlbums)
|
|
||||||
..where((t) => t.account.equals(dbAccount.rowId))
|
|
||||||
..where((t) => t.relativePath.equals(relativePath)))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<NcAlbumItem>> ncAlbumItemsByParent({
|
|
||||||
required NcAlbum parent,
|
|
||||||
}) {
|
|
||||||
final query = select(ncAlbumItems)
|
|
||||||
..where((t) => t.parent.equals(parent.rowId));
|
|
||||||
return query.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<NcAlbumItem>> ncAlbumItemsByParentRelativePath({
|
|
||||||
required ByAccount account,
|
|
||||||
required String parentRelativePath,
|
|
||||||
}) {
|
|
||||||
final query = select(ncAlbumItems).join([
|
|
||||||
innerJoin(ncAlbums, ncAlbums.rowId.equalsExp(ncAlbumItems.parent),
|
|
||||||
useColumns: false),
|
|
||||||
]);
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
query.where(ncAlbums.account.equals(account.sqlAccount!.rowId));
|
|
||||||
} else {
|
|
||||||
query.join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(ncAlbums.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
|
||||||
}
|
|
||||||
query.where(ncAlbums.relativePath.equals(parentRelativePath));
|
|
||||||
return query.map((r) => r.readTable(ncAlbumItems)).get();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,762 +0,0 @@
|
||||||
part of 'database.dart';
|
|
||||||
|
|
||||||
const maxByFileIdsSize = 30000;
|
|
||||||
|
|
||||||
class CompleteFile {
|
|
||||||
const CompleteFile(
|
|
||||||
this.file, this.accountFile, this.image, this.imageLocation, this.trash);
|
|
||||||
|
|
||||||
final File file;
|
|
||||||
final AccountFile accountFile;
|
|
||||||
final Image? image;
|
|
||||||
final ImageLocation? imageLocation;
|
|
||||||
final Trash? trash;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CompleteFileCompanion {
|
|
||||||
const CompleteFileCompanion(
|
|
||||||
this.file, this.accountFile, this.image, this.imageLocation, this.trash);
|
|
||||||
|
|
||||||
final FilesCompanion file;
|
|
||||||
final AccountFilesCompanion accountFile;
|
|
||||||
final ImagesCompanion? image;
|
|
||||||
final ImageLocationsCompanion? imageLocation;
|
|
||||||
final TrashesCompanion? trash;
|
|
||||||
}
|
|
||||||
|
|
||||||
extension CompleteFileListExtension on List<CompleteFile> {
|
|
||||||
Future<List<app.File>> convertToAppFile(app.Account account) {
|
|
||||||
return map((f) => {
|
|
||||||
"userId": account.userId.toString(),
|
|
||||||
"completeFile": f,
|
|
||||||
}).computeAll(_covertSqliteDbFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FileListExtension on List<app.File> {
|
|
||||||
Future<List<CompleteFileCompanion>> convertToFileCompanion(Account? account) {
|
|
||||||
return map((f) => {
|
|
||||||
"account": account,
|
|
||||||
"file": f,
|
|
||||||
}).computeAll(_convertAppFile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FileDescriptor {
|
|
||||||
const FileDescriptor({
|
|
||||||
required this.relativePath,
|
|
||||||
required this.fileId,
|
|
||||||
required this.contentType,
|
|
||||||
required this.isArchived,
|
|
||||||
required this.isFavorite,
|
|
||||||
required this.bestDateTime,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String relativePath;
|
|
||||||
final int fileId;
|
|
||||||
final String? contentType;
|
|
||||||
final bool? isArchived;
|
|
||||||
final bool? isFavorite;
|
|
||||||
final DateTime bestDateTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
extension FileDescriptorListExtension on List<FileDescriptor> {
|
|
||||||
List<app.FileDescriptor> convertToAppFileDescriptor(app.Account account) {
|
|
||||||
return map((f) =>
|
|
||||||
SqliteFileDescriptorConverter.fromSql(account.userId.toString(), f))
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AlbumWithShare {
|
|
||||||
const AlbumWithShare(this.album, this.share);
|
|
||||||
|
|
||||||
final Album album;
|
|
||||||
final AlbumShare? share;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CompleteAlbumCompanion {
|
|
||||||
const CompleteAlbumCompanion(this.album, this.albumShares);
|
|
||||||
|
|
||||||
final AlbumsCompanion album;
|
|
||||||
final List<AlbumSharesCompanion> albumShares;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccountFileRowIds {
|
|
||||||
const AccountFileRowIds(
|
|
||||||
this.accountFileRowId, this.accountRowId, this.fileRowId);
|
|
||||||
|
|
||||||
final int accountFileRowId;
|
|
||||||
final int accountRowId;
|
|
||||||
final int fileRowId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccountFileRowIdsWithFileId {
|
|
||||||
const AccountFileRowIdsWithFileId(
|
|
||||||
this.accountFileRowId, this.accountRowId, this.fileRowId, this.fileId);
|
|
||||||
|
|
||||||
final int accountFileRowId;
|
|
||||||
final int accountRowId;
|
|
||||||
final int fileRowId;
|
|
||||||
final int fileId;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ByAccount {
|
|
||||||
const ByAccount.sql(Account account) : this._(sqlAccount: account);
|
|
||||||
|
|
||||||
const ByAccount.app(app.Account account) : this._(appAccount: account);
|
|
||||||
|
|
||||||
const ByAccount._({
|
|
||||||
this.sqlAccount,
|
|
||||||
this.appAccount,
|
|
||||||
}) : assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
|
|
||||||
final Account? sqlAccount;
|
|
||||||
final app.Account? appAccount;
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SqliteDbExtension on SqliteDb {
|
|
||||||
/// Start a transaction and run [block]
|
|
||||||
///
|
|
||||||
/// The [db] argument passed to [block] is identical to this
|
|
||||||
///
|
|
||||||
/// Do NOT call this when using [isolate], call [useInIsolate] instead
|
|
||||||
Future<T> use<T>(Future<T> Function(SqliteDb db) block) async {
|
|
||||||
return await PlatformLock.synchronized(k.appDbLockId, () async {
|
|
||||||
return await transaction(() async {
|
|
||||||
return await block(this);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Run [block] after acquiring the database
|
|
||||||
///
|
|
||||||
/// The [db] argument passed to [block] is identical to this
|
|
||||||
///
|
|
||||||
/// This function does not start a transaction, see [use] instead
|
|
||||||
Future<T> useNoTransaction<T>(Future<T> Function(SqliteDb db) block) async {
|
|
||||||
return await PlatformLock.synchronized(k.appDbLockId, () async {
|
|
||||||
return await block(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start an isolate and run [callback] there, with access to the
|
|
||||||
/// SQLite database
|
|
||||||
Future<U> isolate<T, U>(T args, ComputeWithDbCallback<T, U> callback) async {
|
|
||||||
// we need to acquire the lock here as method channel is not supported in
|
|
||||||
// background isolates
|
|
||||||
return await PlatformLock.synchronized(k.appDbLockId, () async {
|
|
||||||
// in unit tests we use an in-memory db, which mean there's no way to
|
|
||||||
// access it in other isolates
|
|
||||||
if (isUnitTest) {
|
|
||||||
return await callback(this, args);
|
|
||||||
} else {
|
|
||||||
return await computeWithDb(callback, args);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Start a transaction and run [block], this version is suitable to be called
|
|
||||||
/// in [isolate]
|
|
||||||
///
|
|
||||||
/// See: [use]
|
|
||||||
Future<T> useInIsolate<T>(Future<T> Function(SqliteDb db) block) async {
|
|
||||||
return await transaction(() async {
|
|
||||||
return await block(this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> insertAccountOf(app.Account account) async {
|
|
||||||
Server dbServer;
|
|
||||||
try {
|
|
||||||
dbServer = await into(servers).insertReturning(
|
|
||||||
ServersCompanion.insert(
|
|
||||||
address: account.url,
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
} on StateError catch (_) {
|
|
||||||
// already exists
|
|
||||||
final query = select(servers)
|
|
||||||
..where((t) => t.address.equals(account.url));
|
|
||||||
dbServer = await query.getSingle();
|
|
||||||
}
|
|
||||||
await into(accounts).insert(
|
|
||||||
AccountsCompanion.insert(
|
|
||||||
server: dbServer.rowId,
|
|
||||||
userId: account.userId.toCaseInsensitiveString(),
|
|
||||||
),
|
|
||||||
mode: InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Account> accountOf(app.Account account) {
|
|
||||||
final query = select(accounts).join([
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false)
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.url))
|
|
||||||
..where(accounts.userId.equals(account.userId.toCaseInsensitiveString()))
|
|
||||||
..limit(1);
|
|
||||||
return query.map((r) => r.readTable(accounts)).getSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete Account by app Account
|
|
||||||
///
|
|
||||||
/// If the deleted Account is the last one associated with a Server, then the
|
|
||||||
/// Server will also be deleted
|
|
||||||
Future<void> deleteAccountOf(app.Account account) async {
|
|
||||||
final dbAccount = await accountOf(account);
|
|
||||||
_log.info("[deleteAccountOf] Remove account: ${dbAccount.rowId}");
|
|
||||||
await (delete(accounts)..where((t) => t.rowId.equals(dbAccount.rowId)))
|
|
||||||
.go();
|
|
||||||
final accountCountExp =
|
|
||||||
accounts.rowId.count(filter: accounts.server.equals(dbAccount.server));
|
|
||||||
final accountCountQuery = selectOnly(accounts)
|
|
||||||
..addColumns([accountCountExp]);
|
|
||||||
final accountCount =
|
|
||||||
await accountCountQuery.map((r) => r.read(accountCountExp)).getSingle();
|
|
||||||
_log.info("[deleteAccountOf] Remaining accounts in server: $accountCount");
|
|
||||||
if (accountCount == 0) {
|
|
||||||
_log.info("[deleteAccountOf] Remove server: ${dbAccount.server}");
|
|
||||||
await (delete(servers)..where((t) => t.rowId.equals(dbAccount.server)))
|
|
||||||
.go();
|
|
||||||
}
|
|
||||||
await cleanUpDanglingFiles();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Delete Files without a corresponding entry in AccountFiles
|
|
||||||
Future<void> cleanUpDanglingFiles() async {
|
|
||||||
final query = selectOnly(files).join([
|
|
||||||
leftOuterJoin(accountFiles, accountFiles.file.equalsExp(files.rowId),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..addColumns([files.rowId])
|
|
||||||
..where(accountFiles.relativePath.isNull());
|
|
||||||
final fileRowIds = await query.map((r) => r.read(files.rowId)!).get();
|
|
||||||
if (fileRowIds.isNotEmpty) {
|
|
||||||
_log.info("[cleanUpDanglingFiles] Delete ${fileRowIds.length} files");
|
|
||||||
await fileRowIds.withPartitionNoReturn((sublist) async {
|
|
||||||
await (delete(files)..where((t) => t.rowId.isIn(sublist))).go();
|
|
||||||
}, maxByFileIdsSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FilesQueryBuilder queryFiles() => FilesQueryBuilder(this);
|
|
||||||
|
|
||||||
/// Query File by app File
|
|
||||||
///
|
|
||||||
/// Only one of [sqlAccount] and [appAccount] must be passed
|
|
||||||
Future<File> fileOf(
|
|
||||||
app.File file, {
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(FilesQueryMode.file);
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
q.setSqlAccount(sqlAccount);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(appAccount!);
|
|
||||||
}
|
|
||||||
if (file.fileId != null) {
|
|
||||||
q.byFileId(file.fileId!);
|
|
||||||
} else {
|
|
||||||
q.byRelativePath(file.strippedPathWithEmpty);
|
|
||||||
}
|
|
||||||
return q.build()..limit(1);
|
|
||||||
});
|
|
||||||
return query.map((r) => r.readTable(files)).getSingle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query AccountFiles, Accounts and Files row ID by app File
|
|
||||||
///
|
|
||||||
/// Only one of [sqlAccount] and [appAccount] must be passed
|
|
||||||
Future<AccountFileRowIds?> accountFileRowIdsOfOrNull(
|
|
||||||
app.FileDescriptor file, {
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(FilesQueryMode.expression, expressions: [
|
|
||||||
accountFiles.rowId,
|
|
||||||
accountFiles.account,
|
|
||||||
accountFiles.file,
|
|
||||||
]);
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
q.setSqlAccount(sqlAccount);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(appAccount!);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
q.byFileId(file.fdId);
|
|
||||||
} catch (_) {
|
|
||||||
q.byRelativePath(file.strippedPathWithEmpty);
|
|
||||||
}
|
|
||||||
return q.build()..limit(1);
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) => AccountFileRowIds(
|
|
||||||
r.read(accountFiles.rowId)!,
|
|
||||||
r.read(accountFiles.account)!,
|
|
||||||
r.read(accountFiles.file)!,
|
|
||||||
))
|
|
||||||
.getSingleOrNull();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See [accountFileRowIdsOfOrNull]
|
|
||||||
Future<AccountFileRowIds> accountFileRowIdsOf(
|
|
||||||
app.FileDescriptor file, {
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) =>
|
|
||||||
accountFileRowIdsOfOrNull(file,
|
|
||||||
sqlAccount: sqlAccount, appAccount: appAccount)
|
|
||||||
.notNull();
|
|
||||||
|
|
||||||
/// Query AccountFiles, Accounts and Files row ID by fileIds
|
|
||||||
///
|
|
||||||
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
|
||||||
Future<List<AccountFileRowIdsWithFileId>> accountFileRowIdsByFileIds(
|
|
||||||
ByAccount account, Iterable<int> fileIds) {
|
|
||||||
return fileIds.withPartition((sublist) {
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(FilesQueryMode.expression, expressions: [
|
|
||||||
accountFiles.rowId,
|
|
||||||
accountFiles.account,
|
|
||||||
accountFiles.file,
|
|
||||||
files.fileId,
|
|
||||||
]);
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
q.setSqlAccount(account.sqlAccount!);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(account.appAccount!);
|
|
||||||
}
|
|
||||||
q.byFileIds(sublist);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) => AccountFileRowIdsWithFileId(
|
|
||||||
r.read(accountFiles.rowId)!,
|
|
||||||
r.read(accountFiles.account)!,
|
|
||||||
r.read(accountFiles.file)!,
|
|
||||||
r.read(files.fileId)!,
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}, maxByFileIdsSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query CompleteFile by fileId
|
|
||||||
///
|
|
||||||
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
|
||||||
Future<List<CompleteFile>> completeFilesByFileIds(
|
|
||||||
Iterable<int> fileIds, {
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
return fileIds.withPartition((sublist) {
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(FilesQueryMode.completeFile);
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
q.setSqlAccount(sqlAccount);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(appAccount!);
|
|
||||||
}
|
|
||||||
q.byFileIds(sublist);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) => CompleteFile(
|
|
||||||
r.readTable(files),
|
|
||||||
r.readTable(accountFiles),
|
|
||||||
r.readTableOrNull(images),
|
|
||||||
r.readTableOrNull(imageLocations),
|
|
||||||
r.readTableOrNull(trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}, maxByFileIdsSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<CompleteFile>> completeFilesByDirRowId(
|
|
||||||
int dirRowId, {
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(FilesQueryMode.completeFile);
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
q.setSqlAccount(sqlAccount);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(appAccount!);
|
|
||||||
}
|
|
||||||
q.byDirRowId(dirRowId);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) => CompleteFile(
|
|
||||||
r.readTable(files),
|
|
||||||
r.readTable(accountFiles),
|
|
||||||
r.readTableOrNull(images),
|
|
||||||
r.readTableOrNull(imageLocations),
|
|
||||||
r.readTableOrNull(trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query CompleteFile by favorite
|
|
||||||
Future<List<CompleteFile>> completeFilesByFavorite({
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(FilesQueryMode.completeFile);
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
q.setSqlAccount(sqlAccount);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(appAccount!);
|
|
||||||
}
|
|
||||||
q.byFavorite(true);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) => CompleteFile(
|
|
||||||
r.readTable(files),
|
|
||||||
r.readTable(accountFiles),
|
|
||||||
r.readTableOrNull(images),
|
|
||||||
r.readTableOrNull(imageLocations),
|
|
||||||
r.readTableOrNull(trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query [FileDescriptor]s by fileId
|
|
||||||
///
|
|
||||||
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
|
||||||
Future<List<FileDescriptor>> fileDescriptorsByFileIds(
|
|
||||||
ByAccount account, Iterable<int> fileIds) {
|
|
||||||
return fileIds.withPartition((sublist) {
|
|
||||||
final query = queryFiles().run((q) {
|
|
||||||
q.setQueryMode(
|
|
||||||
FilesQueryMode.expression,
|
|
||||||
expressions: [
|
|
||||||
accountFiles.relativePath,
|
|
||||||
files.fileId,
|
|
||||||
files.contentType,
|
|
||||||
accountFiles.isArchived,
|
|
||||||
accountFiles.isFavorite,
|
|
||||||
accountFiles.bestDateTime,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
q.setSqlAccount(account.sqlAccount!);
|
|
||||||
} else {
|
|
||||||
q.setAppAccount(account.appAccount!);
|
|
||||||
}
|
|
||||||
q.byFileIds(sublist);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return query
|
|
||||||
.map((r) => FileDescriptor(
|
|
||||||
relativePath: r.read(accountFiles.relativePath)!,
|
|
||||||
fileId: r.read(files.fileId)!,
|
|
||||||
contentType: r.read(files.contentType),
|
|
||||||
isArchived: r.read(accountFiles.isArchived),
|
|
||||||
isFavorite: r.read(accountFiles.isFavorite),
|
|
||||||
bestDateTime: r.read(accountFiles.bestDateTime)!,
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
}, maxByFileIdsSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> moveFileByFileId(
|
|
||||||
ByAccount account, int fileId, String destinationRelativePath) async {
|
|
||||||
final rowId = (await accountFileRowIdsByFileIds(account, [fileId])).first;
|
|
||||||
final q = update(accountFiles)
|
|
||||||
..where((t) => t.rowId.equals(rowId.accountFileRowId));
|
|
||||||
await q.write(AccountFilesCompanion(
|
|
||||||
relativePath: Value(destinationRelativePath),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<Tag>> allTags({
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
final query = select(tags)
|
|
||||||
..where((t) => t.server.equals(sqlAccount.server));
|
|
||||||
return query.get();
|
|
||||||
} else {
|
|
||||||
final query = select(tags).join([
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(tags.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(appAccount!.url));
|
|
||||||
return query.map((r) => r.readTable(tags)).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Tag?> tagByDisplayName({
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
required String displayName,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
final query = select(tags)
|
|
||||||
..where((t) => t.server.equals(sqlAccount.server))
|
|
||||||
..where((t) => t.displayName.like(displayName))
|
|
||||||
..limit(1);
|
|
||||||
return query.getSingleOrNull();
|
|
||||||
} else {
|
|
||||||
final query = select(tags).join([
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(tags.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(appAccount!.url))
|
|
||||||
..where(tags.displayName.like(displayName))
|
|
||||||
..limit(1);
|
|
||||||
return query.map((r) => r.readTable(tags)).getSingleOrNull();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<FaceRecognitionPerson>> allFaceRecognitionPersons({
|
|
||||||
required ByAccount account,
|
|
||||||
}) {
|
|
||||||
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
final query = select(faceRecognitionPersons)
|
|
||||||
..where((t) => t.account.equals(account.sqlAccount!.rowId));
|
|
||||||
return query.get();
|
|
||||||
} else {
|
|
||||||
final query = select(faceRecognitionPersons).join([
|
|
||||||
innerJoin(
|
|
||||||
accounts, accounts.rowId.equalsExp(faceRecognitionPersons.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
|
||||||
return query.map((r) => r.readTable(faceRecognitionPersons)).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<FaceRecognitionPerson>> faceRecognitionPersonsByName({
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
required String name,
|
|
||||||
}) {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
final query = select(faceRecognitionPersons)
|
|
||||||
..where((t) => t.account.equals(sqlAccount.rowId))
|
|
||||||
..where((t) =>
|
|
||||||
t.name.like(name) |
|
|
||||||
t.name.like("% $name") |
|
|
||||||
t.name.like("$name %"));
|
|
||||||
return query.get();
|
|
||||||
} else {
|
|
||||||
final query = select(faceRecognitionPersons).join([
|
|
||||||
innerJoin(
|
|
||||||
accounts, accounts.rowId.equalsExp(faceRecognitionPersons.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(appAccount!.url))
|
|
||||||
..where(
|
|
||||||
accounts.userId.equals(appAccount.userId.toCaseInsensitiveString()))
|
|
||||||
..where(faceRecognitionPersons.name.like(name) |
|
|
||||||
faceRecognitionPersons.name.like("% $name") |
|
|
||||||
faceRecognitionPersons.name.like("$name %"));
|
|
||||||
return query.map((r) => r.readTable(faceRecognitionPersons)).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<RecognizeFace>> allRecognizeFaces({
|
|
||||||
required ByAccount account,
|
|
||||||
}) {
|
|
||||||
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
final query = select(recognizeFaces)
|
|
||||||
..where((t) => t.account.equals(account.sqlAccount!.rowId));
|
|
||||||
return query.get();
|
|
||||||
} else {
|
|
||||||
final query = select(recognizeFaces).join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(recognizeFaces.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()));
|
|
||||||
return query.map((r) => r.readTable(recognizeFaces)).get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<RecognizeFace> recognizeFaceByLabel({
|
|
||||||
required ByAccount account,
|
|
||||||
required String label,
|
|
||||||
}) {
|
|
||||||
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
final query = select(recognizeFaces)
|
|
||||||
..where((t) => t.account.equals(account.sqlAccount!.rowId))
|
|
||||||
..where((t) => t.label.equals(label));
|
|
||||||
return query.getSingle();
|
|
||||||
} else {
|
|
||||||
final query = select(recognizeFaces).join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(recognizeFaces.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()))
|
|
||||||
..where(recognizeFaces.label.equals(label));
|
|
||||||
return query.map((r) => r.readTable(recognizeFaces)).getSingle();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<RecognizeFaceItem>> recognizeFaceItemsByParentLabel({
|
|
||||||
required ByAccount account,
|
|
||||||
required String label,
|
|
||||||
List<OrderingTerm>? orderBy,
|
|
||||||
int? limit,
|
|
||||||
int? offset,
|
|
||||||
}) {
|
|
||||||
assert((account.sqlAccount != null) != (account.appAccount != null));
|
|
||||||
final query = select(recognizeFaceItems).join([
|
|
||||||
innerJoin(recognizeFaces,
|
|
||||||
recognizeFaces.rowId.equalsExp(recognizeFaceItems.parent),
|
|
||||||
useColumns: false),
|
|
||||||
]);
|
|
||||||
if (account.sqlAccount != null) {
|
|
||||||
query
|
|
||||||
..where(recognizeFaces.account.equals(account.sqlAccount!.rowId))
|
|
||||||
..where(recognizeFaces.label.equals(label));
|
|
||||||
} else {
|
|
||||||
query
|
|
||||||
..join([
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(recognizeFaces.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
])
|
|
||||||
..where(servers.address.equals(account.appAccount!.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(account.appAccount!.userId.toCaseInsensitiveString()))
|
|
||||||
..where(recognizeFaces.label.equals(label));
|
|
||||||
}
|
|
||||||
if (orderBy != null) {
|
|
||||||
query.orderBy(orderBy);
|
|
||||||
if (limit != null) {
|
|
||||||
query.limit(limit, offset: offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return query.map((r) => r.readTable(recognizeFaceItems)).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> countMissingMetadataByFileIds({
|
|
||||||
Account? sqlAccount,
|
|
||||||
app.Account? appAccount,
|
|
||||||
required List<int> fileIds,
|
|
||||||
}) async {
|
|
||||||
assert((sqlAccount != null) != (appAccount != null));
|
|
||||||
if (fileIds.isEmpty) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
final counts = await fileIds.withPartition((sublist) async {
|
|
||||||
final count = countAll(
|
|
||||||
filter:
|
|
||||||
images.lastUpdated.isNull() | imageLocations.version.isNull());
|
|
||||||
final query = selectOnly(files).join([
|
|
||||||
innerJoin(accountFiles, accountFiles.file.equalsExp(files.rowId),
|
|
||||||
useColumns: false),
|
|
||||||
if (appAccount != null) ...[
|
|
||||||
innerJoin(accounts, accounts.rowId.equalsExp(accountFiles.account),
|
|
||||||
useColumns: false),
|
|
||||||
innerJoin(servers, servers.rowId.equalsExp(accounts.server),
|
|
||||||
useColumns: false),
|
|
||||||
],
|
|
||||||
leftOuterJoin(images, images.accountFile.equalsExp(accountFiles.rowId),
|
|
||||||
useColumns: false),
|
|
||||||
leftOuterJoin(imageLocations,
|
|
||||||
imageLocations.accountFile.equalsExp(accountFiles.rowId),
|
|
||||||
useColumns: false),
|
|
||||||
]);
|
|
||||||
query.addColumns([count]);
|
|
||||||
if (sqlAccount != null) {
|
|
||||||
query.where(accountFiles.account.equals(sqlAccount.rowId));
|
|
||||||
} else if (appAccount != null) {
|
|
||||||
query
|
|
||||||
..where(servers.address.equals(appAccount.url))
|
|
||||||
..where(accounts.userId
|
|
||||||
.equals(appAccount.userId.toCaseInsensitiveString()));
|
|
||||||
}
|
|
||||||
query
|
|
||||||
..where(files.fileId.isIn(sublist))
|
|
||||||
..where(whereFileIsSupportedImageMime());
|
|
||||||
return [await query.map((r) => r.read(count)!).getSingle()];
|
|
||||||
}, maxByFileIdsSize);
|
|
||||||
return counts.reduce((value, element) => value + element);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> truncate() async {
|
|
||||||
await delete(servers).go();
|
|
||||||
// technically deleting Servers table is enough to clear the followings, but
|
|
||||||
// just in case
|
|
||||||
await delete(accounts).go();
|
|
||||||
await delete(files).go();
|
|
||||||
await delete(images).go();
|
|
||||||
await delete(imageLocations).go();
|
|
||||||
await delete(trashes).go();
|
|
||||||
await delete(accountFiles).go();
|
|
||||||
await delete(dirFiles).go();
|
|
||||||
await delete(albums).go();
|
|
||||||
await delete(albumShares).go();
|
|
||||||
await delete(tags).go();
|
|
||||||
await delete(faceRecognitionPersons).go();
|
|
||||||
await delete(ncAlbums).go();
|
|
||||||
await delete(ncAlbumItems).go();
|
|
||||||
await delete(recognizeFaces).go();
|
|
||||||
await delete(recognizeFaceItems).go();
|
|
||||||
|
|
||||||
// reset the auto increment counter
|
|
||||||
await customStatement("UPDATE sqlite_sequence SET seq=0;");
|
|
||||||
}
|
|
||||||
|
|
||||||
Expression<bool> whereFileIsSupportedMime() {
|
|
||||||
return file_util.supportedFormatMimes
|
|
||||||
.map<Expression<bool>>((m) => files.contentType.equals(m))
|
|
||||||
.reduce((value, element) => value | element);
|
|
||||||
}
|
|
||||||
|
|
||||||
Expression<bool> whereFileIsSupportedImageMime() {
|
|
||||||
return file_util.supportedImageFormatMimes
|
|
||||||
.map<Expression<bool>>((m) => files.contentType.equals(m))
|
|
||||||
.reduce((value, element) => value | element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.File _covertSqliteDbFile(Map map) {
|
|
||||||
final userId = map["userId"] as String;
|
|
||||||
final file = map["completeFile"] as CompleteFile;
|
|
||||||
return SqliteFileConverter.fromSql(userId, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompleteFileCompanion _convertAppFile(Map map) {
|
|
||||||
final account = map["account"] as Account?;
|
|
||||||
final file = map["file"] as app.File;
|
|
||||||
return SqliteFileConverter.toSql(account, file);
|
|
||||||
}
|
|
|
@ -1,422 +0,0 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:drift/drift.dart';
|
|
||||||
import 'package:nc_photos/entity/album.dart';
|
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
|
||||||
import 'package:nc_photos/entity/exif.dart';
|
|
||||||
import 'package:nc_photos/entity/face_recognition_person.dart';
|
|
||||||
import 'package:nc_photos/entity/file.dart';
|
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
|
||||||
import 'package:nc_photos/entity/nc_album.dart';
|
|
||||||
import 'package:nc_photos/entity/nc_album_item.dart';
|
|
||||||
import 'package:nc_photos/entity/recognize_face.dart';
|
|
||||||
import 'package:nc_photos/entity/recognize_face_item.dart';
|
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:np_api/np_api.dart' as api;
|
|
||||||
import 'package:np_async/np_async.dart';
|
|
||||||
import 'package:np_common/or_null.dart';
|
|
||||||
import 'package:np_common/type.dart';
|
|
||||||
import 'package:np_string/np_string.dart';
|
|
||||||
|
|
||||||
extension SqlTagListExtension on List<sql.Tag> {
|
|
||||||
Future<List<Tag>> convertToAppTag() {
|
|
||||||
return computeAll(SqliteTagConverter.fromSql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppTagListExtension on List<Tag> {
|
|
||||||
Future<List<sql.TagsCompanion>> convertToTagCompanion(
|
|
||||||
sql.Account? dbAccount) {
|
|
||||||
return map((t) => {
|
|
||||||
"account": dbAccount,
|
|
||||||
"tag": t,
|
|
||||||
}).computeAll(_convertAppTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SqlFaceRecognitionPersonListExtension
|
|
||||||
on List<sql.FaceRecognitionPerson> {
|
|
||||||
Future<List<FaceRecognitionPerson>> convertToAppFaceRecognitionPerson() {
|
|
||||||
return computeAll(SqliteFaceRecognitionPersonConverter.fromSql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppFaceRecognitionPersonListExtension on List<FaceRecognitionPerson> {
|
|
||||||
Future<List<sql.FaceRecognitionPersonsCompanion>>
|
|
||||||
convertToFaceRecognitionPersonCompanion(sql.Account? dbAccount) {
|
|
||||||
return map((p) => {
|
|
||||||
"account": dbAccount,
|
|
||||||
"person": p,
|
|
||||||
}).computeAll(_convertAppFaceRecognitionPerson);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension SqlRecognizeFaceListExtension on List<sql.RecognizeFace> {
|
|
||||||
Future<List<RecognizeFace>> convertToAppRecognizeFace() {
|
|
||||||
return computeAll(SqliteRecognizeFaceConverter.fromSql);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension AppRecognizeFaceListExtension on List<RecognizeFace> {
|
|
||||||
Future<List<sql.RecognizeFacesCompanion>> convertToRecognizeFaceCompanion(
|
|
||||||
sql.Account? dbAccount) {
|
|
||||||
return map((f) => {
|
|
||||||
"account": dbAccount,
|
|
||||||
"face": f,
|
|
||||||
}).computeAll(_convertAppRecognizeFace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteAlbumConverter {
|
|
||||||
static Album fromSql(
|
|
||||||
sql.Album album, File albumFile, List<sql.AlbumShare> shares) {
|
|
||||||
return Album(
|
|
||||||
lastUpdated: album.lastUpdated,
|
|
||||||
name: album.name,
|
|
||||||
provider: AlbumProvider.fromJson({
|
|
||||||
"type": album.providerType,
|
|
||||||
"content": jsonDecode(album.providerContent),
|
|
||||||
}),
|
|
||||||
coverProvider: AlbumCoverProvider.fromJson({
|
|
||||||
"type": album.coverProviderType,
|
|
||||||
"content": jsonDecode(album.coverProviderContent),
|
|
||||||
}),
|
|
||||||
sortProvider: AlbumSortProvider.fromJson({
|
|
||||||
"type": album.sortProviderType,
|
|
||||||
"content": jsonDecode(album.sortProviderContent),
|
|
||||||
}),
|
|
||||||
shares: shares.isEmpty
|
|
||||||
? null
|
|
||||||
: shares
|
|
||||||
.map((e) => AlbumShare(
|
|
||||||
userId: e.userId.toCi(),
|
|
||||||
displayName: e.displayName,
|
|
||||||
sharedAt: e.sharedAt.toUtc(),
|
|
||||||
))
|
|
||||||
.toList(),
|
|
||||||
// replace with the original etag when this album was cached
|
|
||||||
albumFile: albumFile.copyWith(etag: OrNull(album.fileEtag)),
|
|
||||||
savedVersion: album.version,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static sql.CompleteAlbumCompanion toSql(
|
|
||||||
Album album, int albumFileRowId, String albumFileEtag) {
|
|
||||||
final providerJson = album.provider.toJson();
|
|
||||||
final coverProviderJson = album.coverProvider.toJson();
|
|
||||||
final sortProviderJson = album.sortProvider.toJson();
|
|
||||||
final dbAlbum = sql.AlbumsCompanion.insert(
|
|
||||||
file: albumFileRowId,
|
|
||||||
fileEtag: Value(albumFileEtag),
|
|
||||||
version: Album.version,
|
|
||||||
lastUpdated: album.lastUpdated,
|
|
||||||
name: album.name,
|
|
||||||
providerType: providerJson["type"],
|
|
||||||
providerContent: jsonEncode(providerJson["content"]),
|
|
||||||
coverProviderType: coverProviderJson["type"],
|
|
||||||
coverProviderContent: jsonEncode(coverProviderJson["content"]),
|
|
||||||
sortProviderType: sortProviderJson["type"],
|
|
||||||
sortProviderContent: jsonEncode(sortProviderJson["content"]),
|
|
||||||
);
|
|
||||||
final dbAlbumShares = album.shares
|
|
||||||
?.map((s) => sql.AlbumSharesCompanion(
|
|
||||||
userId: Value(s.userId.toCaseInsensitiveString()),
|
|
||||||
displayName: Value(s.displayName),
|
|
||||||
sharedAt: Value(s.sharedAt),
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
return sql.CompleteAlbumCompanion(dbAlbum, dbAlbumShares ?? []);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteFileDescriptorConverter {
|
|
||||||
static FileDescriptor fromSql(String userId, sql.FileDescriptor f) {
|
|
||||||
return FileDescriptor(
|
|
||||||
fdPath: "remote.php/dav/files/$userId/${f.relativePath}",
|
|
||||||
fdId: f.fileId,
|
|
||||||
fdMime: f.contentType,
|
|
||||||
fdIsArchived: f.isArchived ?? false,
|
|
||||||
fdIsFavorite: f.isFavorite ?? false,
|
|
||||||
fdDateTime: f.bestDateTime,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteFileConverter {
|
|
||||||
static File fromSql(String userId, sql.CompleteFile f) {
|
|
||||||
final metadata = f.image?.run((obj) => Metadata(
|
|
||||||
lastUpdated: obj.lastUpdated,
|
|
||||||
fileEtag: obj.fileEtag,
|
|
||||||
imageWidth: obj.width,
|
|
||||||
imageHeight: obj.height,
|
|
||||||
exif: obj.exifRaw?.run((e) => Exif.fromJson(jsonDecode(e))),
|
|
||||||
));
|
|
||||||
final location = f.imageLocation?.run((obj) => ImageLocation(
|
|
||||||
version: obj.version,
|
|
||||||
name: obj.name,
|
|
||||||
latitude: obj.latitude,
|
|
||||||
longitude: obj.longitude,
|
|
||||||
countryCode: obj.countryCode,
|
|
||||||
admin1: obj.admin1,
|
|
||||||
admin2: obj.admin2,
|
|
||||||
));
|
|
||||||
return File(
|
|
||||||
path: "remote.php/dav/files/$userId/${f.accountFile.relativePath}",
|
|
||||||
contentLength: f.file.contentLength,
|
|
||||||
contentType: f.file.contentType,
|
|
||||||
etag: f.file.etag,
|
|
||||||
lastModified: f.file.lastModified,
|
|
||||||
isCollection: f.file.isCollection,
|
|
||||||
usedBytes: f.file.usedBytes,
|
|
||||||
hasPreview: f.file.hasPreview,
|
|
||||||
fileId: f.file.fileId,
|
|
||||||
isFavorite: f.accountFile.isFavorite,
|
|
||||||
ownerId: f.file.ownerId?.toCi(),
|
|
||||||
ownerDisplayName: f.file.ownerDisplayName,
|
|
||||||
trashbinFilename: f.trash?.filename,
|
|
||||||
trashbinOriginalLocation: f.trash?.originalLocation,
|
|
||||||
trashbinDeletionTime: f.trash?.deletionTime,
|
|
||||||
metadata: metadata,
|
|
||||||
isArchived: f.accountFile.isArchived,
|
|
||||||
overrideDateTime: f.accountFile.overrideDateTime,
|
|
||||||
location: location,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static sql.CompleteFileCompanion toSql(sql.Account? account, File file) {
|
|
||||||
final dbFile = sql.FilesCompanion(
|
|
||||||
server: account == null ? const Value.absent() : Value(account.server),
|
|
||||||
fileId: Value(file.fileId!),
|
|
||||||
contentLength: Value(file.contentLength),
|
|
||||||
contentType: Value(file.contentType),
|
|
||||||
etag: Value(file.etag),
|
|
||||||
lastModified: Value(file.lastModified),
|
|
||||||
isCollection: Value(file.isCollection),
|
|
||||||
usedBytes: Value(file.usedBytes),
|
|
||||||
hasPreview: Value(file.hasPreview),
|
|
||||||
ownerId: Value(file.ownerId!.toCaseInsensitiveString()),
|
|
||||||
ownerDisplayName: Value(file.ownerDisplayName),
|
|
||||||
);
|
|
||||||
final dbAccountFile = sql.AccountFilesCompanion(
|
|
||||||
account: account == null ? const Value.absent() : Value(account.rowId),
|
|
||||||
relativePath: Value(file.strippedPathWithEmpty),
|
|
||||||
isFavorite: Value(file.isFavorite),
|
|
||||||
isArchived: Value(file.isArchived),
|
|
||||||
overrideDateTime: Value(file.overrideDateTime),
|
|
||||||
bestDateTime: Value(file.bestDateTime),
|
|
||||||
);
|
|
||||||
final dbImage = file.metadata?.run((m) => sql.ImagesCompanion.insert(
|
|
||||||
lastUpdated: m.lastUpdated,
|
|
||||||
fileEtag: Value(m.fileEtag),
|
|
||||||
width: Value(m.imageWidth),
|
|
||||||
height: Value(m.imageHeight),
|
|
||||||
exifRaw: Value(m.exif?.toJson().run((j) => jsonEncode(j))),
|
|
||||||
dateTimeOriginal: Value(m.exif?.dateTimeOriginal),
|
|
||||||
));
|
|
||||||
final dbImageLocation =
|
|
||||||
file.location?.run((l) => sql.ImageLocationsCompanion.insert(
|
|
||||||
version: l.version,
|
|
||||||
name: Value(l.name),
|
|
||||||
latitude: Value(l.latitude),
|
|
||||||
longitude: Value(l.longitude),
|
|
||||||
countryCode: Value(l.countryCode),
|
|
||||||
admin1: Value(l.admin1),
|
|
||||||
admin2: Value(l.admin2),
|
|
||||||
));
|
|
||||||
final dbTrash = file.trashbinDeletionTime == null
|
|
||||||
? null
|
|
||||||
: sql.TrashesCompanion.insert(
|
|
||||||
filename: file.trashbinFilename!,
|
|
||||||
originalLocation: file.trashbinOriginalLocation!,
|
|
||||||
deletionTime: file.trashbinDeletionTime!,
|
|
||||||
);
|
|
||||||
return sql.CompleteFileCompanion(
|
|
||||||
dbFile, dbAccountFile, dbImage, dbImageLocation, dbTrash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteTagConverter {
|
|
||||||
static Tag fromSql(sql.Tag tag) => Tag(
|
|
||||||
id: tag.tagId,
|
|
||||||
displayName: tag.displayName,
|
|
||||||
userVisible: tag.userVisible,
|
|
||||||
userAssignable: tag.userAssignable,
|
|
||||||
);
|
|
||||||
|
|
||||||
static sql.TagsCompanion toSql(sql.Account? dbAccount, Tag tag) =>
|
|
||||||
sql.TagsCompanion(
|
|
||||||
server:
|
|
||||||
dbAccount == null ? const Value.absent() : Value(dbAccount.server),
|
|
||||||
tagId: Value(tag.id),
|
|
||||||
displayName: Value(tag.displayName),
|
|
||||||
userVisible: Value(tag.userVisible),
|
|
||||||
userAssignable: Value(tag.userAssignable),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteFaceRecognitionPersonConverter {
|
|
||||||
static FaceRecognitionPerson fromSql(sql.FaceRecognitionPerson person) =>
|
|
||||||
FaceRecognitionPerson(
|
|
||||||
name: person.name,
|
|
||||||
thumbFaceId: person.thumbFaceId,
|
|
||||||
count: person.count,
|
|
||||||
);
|
|
||||||
|
|
||||||
static sql.FaceRecognitionPersonsCompanion toSql(
|
|
||||||
sql.Account? dbAccount, FaceRecognitionPerson person) =>
|
|
||||||
sql.FaceRecognitionPersonsCompanion(
|
|
||||||
account:
|
|
||||||
dbAccount == null ? const Value.absent() : Value(dbAccount.rowId),
|
|
||||||
name: Value(person.name),
|
|
||||||
thumbFaceId: Value(person.thumbFaceId),
|
|
||||||
count: Value(person.count),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteNcAlbumConverter {
|
|
||||||
static NcAlbum fromSql(String userId, sql.NcAlbum ncAlbum) {
|
|
||||||
final json = ncAlbum.collaborators
|
|
||||||
.run((obj) => (jsonDecode(obj) as List).cast<Map>());
|
|
||||||
return NcAlbum(
|
|
||||||
path:
|
|
||||||
"${api.ApiPhotos.path}/$userId/${ncAlbum.isOwned ? "albums" : "sharedalbums"}/${ncAlbum.relativePath}",
|
|
||||||
lastPhoto: ncAlbum.lastPhoto,
|
|
||||||
nbItems: ncAlbum.nbItems,
|
|
||||||
location: ncAlbum.location,
|
|
||||||
dateStart: ncAlbum.dateStart,
|
|
||||||
dateEnd: ncAlbum.dateEnd,
|
|
||||||
collaborators: json
|
|
||||||
.map((e) => NcAlbumCollaborator.fromJson(e.cast<String, dynamic>()))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static sql.NcAlbumsCompanion toSql(sql.Account? dbAccount, NcAlbum ncAlbum) =>
|
|
||||||
sql.NcAlbumsCompanion(
|
|
||||||
account:
|
|
||||||
dbAccount == null ? const Value.absent() : Value(dbAccount.rowId),
|
|
||||||
relativePath: Value(ncAlbum.strippedPath),
|
|
||||||
lastPhoto: Value(ncAlbum.lastPhoto),
|
|
||||||
nbItems: Value(ncAlbum.nbItems),
|
|
||||||
location: Value(ncAlbum.location),
|
|
||||||
dateStart: Value(ncAlbum.dateStart),
|
|
||||||
dateEnd: Value(ncAlbum.dateEnd),
|
|
||||||
collaborators: Value(
|
|
||||||
jsonEncode(ncAlbum.collaborators.map((c) => c.toJson()).toList())),
|
|
||||||
isOwned: Value(ncAlbum.isOwned),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteNcAlbumItemConverter {
|
|
||||||
static NcAlbumItem fromSql(String userId, String albumRelativePath,
|
|
||||||
bool isAlbumOwned, sql.NcAlbumItem item) =>
|
|
||||||
NcAlbumItem(
|
|
||||||
path:
|
|
||||||
"${api.ApiPhotos.path}/$userId/${isAlbumOwned ? "albums" : "sharedalbums"}/$albumRelativePath/${item.relativePath}",
|
|
||||||
fileId: item.fileId,
|
|
||||||
contentLength: item.contentLength,
|
|
||||||
contentType: item.contentType,
|
|
||||||
etag: item.etag,
|
|
||||||
lastModified: item.lastModified,
|
|
||||||
hasPreview: item.hasPreview,
|
|
||||||
isFavorite: item.isFavorite,
|
|
||||||
fileMetadataWidth: item.fileMetadataWidth,
|
|
||||||
fileMetadataHeight: item.fileMetadataHeight,
|
|
||||||
);
|
|
||||||
|
|
||||||
static sql.NcAlbumItemsCompanion toSql(
|
|
||||||
sql.NcAlbum parent,
|
|
||||||
NcAlbumItem item,
|
|
||||||
) =>
|
|
||||||
sql.NcAlbumItemsCompanion(
|
|
||||||
parent: Value(parent.rowId),
|
|
||||||
relativePath: Value(item.strippedPath),
|
|
||||||
fileId: Value(item.fileId),
|
|
||||||
contentLength: Value(item.contentLength),
|
|
||||||
contentType: Value(item.contentType),
|
|
||||||
etag: Value(item.etag),
|
|
||||||
lastModified: Value(item.lastModified),
|
|
||||||
hasPreview: Value(item.hasPreview),
|
|
||||||
isFavorite: Value(item.isFavorite),
|
|
||||||
fileMetadataWidth: Value(item.fileMetadataWidth),
|
|
||||||
fileMetadataHeight: Value(item.fileMetadataHeight),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteRecognizeFaceConverter {
|
|
||||||
static RecognizeFace fromSql(sql.RecognizeFace face) => RecognizeFace(
|
|
||||||
label: face.label,
|
|
||||||
);
|
|
||||||
|
|
||||||
static sql.RecognizeFacesCompanion toSql(
|
|
||||||
sql.Account? dbAccount, RecognizeFace face) =>
|
|
||||||
sql.RecognizeFacesCompanion(
|
|
||||||
account:
|
|
||||||
dbAccount == null ? const Value.absent() : Value(dbAccount.rowId),
|
|
||||||
label: Value(face.label),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
class SqliteRecognizeFaceItemConverter {
|
|
||||||
static RecognizeFaceItem fromSql(
|
|
||||||
String userId, String faceLabel, sql.RecognizeFaceItem item) =>
|
|
||||||
RecognizeFaceItem(
|
|
||||||
path:
|
|
||||||
"${api.ApiRecognize.path}/$userId/faces/$faceLabel/${item.relativePath}",
|
|
||||||
fileId: item.fileId,
|
|
||||||
contentLength: item.contentLength,
|
|
||||||
contentType: item.contentType,
|
|
||||||
etag: item.etag,
|
|
||||||
lastModified: item.lastModified,
|
|
||||||
hasPreview: item.hasPreview,
|
|
||||||
realPath: item.realPath,
|
|
||||||
isFavorite: item.isFavorite,
|
|
||||||
fileMetadataWidth: item.fileMetadataWidth,
|
|
||||||
fileMetadataHeight: item.fileMetadataHeight,
|
|
||||||
faceDetections: item.faceDetections
|
|
||||||
?.run((obj) => (jsonDecode(obj) as List).cast<JsonObj>()),
|
|
||||||
);
|
|
||||||
|
|
||||||
static sql.RecognizeFaceItemsCompanion toSql(
|
|
||||||
sql.RecognizeFace parent,
|
|
||||||
RecognizeFaceItem item,
|
|
||||||
) =>
|
|
||||||
sql.RecognizeFaceItemsCompanion(
|
|
||||||
parent: Value(parent.rowId),
|
|
||||||
relativePath: Value(item.strippedPath),
|
|
||||||
fileId: Value(item.fileId),
|
|
||||||
contentLength: Value(item.contentLength),
|
|
||||||
contentType: Value(item.contentType),
|
|
||||||
etag: Value(item.etag),
|
|
||||||
lastModified: Value(item.lastModified),
|
|
||||||
hasPreview: Value(item.hasPreview),
|
|
||||||
realPath: Value(item.realPath),
|
|
||||||
isFavorite: Value(item.isFavorite),
|
|
||||||
fileMetadataWidth: Value(item.fileMetadataWidth),
|
|
||||||
fileMetadataHeight: Value(item.fileMetadataHeight),
|
|
||||||
faceDetections:
|
|
||||||
Value(item.faceDetections?.run((obj) => jsonEncode(obj))),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.TagsCompanion _convertAppTag(Map map) {
|
|
||||||
final account = map["account"] as sql.Account?;
|
|
||||||
final tag = map["tag"] as Tag;
|
|
||||||
return SqliteTagConverter.toSql(account, tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.FaceRecognitionPersonsCompanion _convertAppFaceRecognitionPerson(Map map) {
|
|
||||||
final account = map["account"] as sql.Account?;
|
|
||||||
final person = map["person"] as FaceRecognitionPerson;
|
|
||||||
return SqliteFaceRecognitionPersonConverter.toSql(account, person);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql.RecognizeFacesCompanion _convertAppRecognizeFace(Map map) {
|
|
||||||
final account = map["account"] as sql.Account?;
|
|
||||||
final face = map["face"] as RecognizeFace;
|
|
||||||
return SqliteRecognizeFaceConverter.toSql(account, face);
|
|
||||||
}
|
|
|
@ -1,14 +1,14 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/entity_converter.dart';
|
import 'package:nc_photos/api/entity_converter.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
import 'package:nc_photos/entity/tag.dart';
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/np_api_util.dart';
|
import 'package:nc_photos/np_api_util.dart';
|
||||||
import 'package:np_api/np_api.dart' as api;
|
import 'package:np_api/np_api.dart' as api;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'data_source.g.dart';
|
part 'data_source.g.dart';
|
||||||
|
|
||||||
|
@ -64,22 +64,20 @@ class TagRemoteDataSource implements TagDataSource {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class TagSqliteDbDataSource implements TagDataSource {
|
class TagSqliteDbDataSource implements TagDataSource {
|
||||||
const TagSqliteDbDataSource(this.sqliteDb);
|
const TagSqliteDbDataSource(this.db);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
list(Account account) async {
|
Future<List<Tag>> list(Account account) async {
|
||||||
_log.info("[list] $account");
|
_log.info("[list] $account");
|
||||||
final dbTags = await sqliteDb.use((db) async {
|
final results = await db.getTags(account: account.toDb());
|
||||||
return await db.allTags(appAccount: account);
|
return results.map(DbTagConverter.fromDb).toList();
|
||||||
});
|
|
||||||
return dbTags.convertToAppTag();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
listByFile(Account account, File file) async {
|
Future<List<Tag>> listByFile(Account account, File file) async {
|
||||||
_log.info("[listByFile] ${file.path}");
|
_log.info("[listByFile] ${file.path}");
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
|
|
||||||
final sql.SqliteDb sqliteDb;
|
final NpDb db;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/pref_util.dart' as pref_util;
|
import 'package:nc_photos/entity/pref_util.dart' as pref_util;
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/help_utils.dart' as help_utils;
|
import 'package:nc_photos/help_utils.dart' as help_utils;
|
||||||
import 'package:nc_photos/legacy/connect.dart';
|
import 'package:nc_photos/legacy/connect.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
|
@ -17,6 +16,7 @@ import 'package:nc_photos/widget/home.dart';
|
||||||
import 'package:nc_photos/widget/root_picker.dart';
|
import 'package:nc_photos/widget/root_picker.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
|
|
||||||
|
@ -306,10 +306,7 @@ class _SignInState extends State<SignIn> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _persistAccount(Account account) async {
|
Future<void> _persistAccount(Account account) async {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
await context.read<NpDb>().addAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.use((db) async {
|
|
||||||
await db.insertAccountOf(account);
|
|
||||||
});
|
|
||||||
// only signing in with app password would trigger distinct
|
// only signing in with app password would trigger distinct
|
||||||
final accounts = (Pref().getAccounts3Or([])..add(account)).distinct();
|
final accounts = (Pref().getAccounts3Or([])..add(account)).distinct();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export 'db_util.dart';
|
|
||||||
export 'download.dart';
|
export 'download.dart';
|
||||||
export 'file_saver.dart';
|
export 'file_saver.dart';
|
||||||
export 'notification.dart';
|
export 'notification.dart';
|
||||||
|
|
|
@ -98,7 +98,7 @@ class _Service {
|
||||||
}
|
}
|
||||||
await onCancelSubscription.cancel();
|
await onCancelSubscription.cancel();
|
||||||
await onDataSubscription.cancel();
|
await onDataSubscription.cancel();
|
||||||
await KiwiContainer().resolve<DiContainer>().sqliteDb.close();
|
await KiwiContainer().resolve<DiContainer>().npDb.dispose();
|
||||||
service.stopBackgroundService();
|
service.stopBackgroundService();
|
||||||
_log.info("[call] Service stopped");
|
_log.info("[call] Service stopped");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,113 +1,33 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
|
||||||
|
|
||||||
part 'cache_favorite.g.dart';
|
part 'cache_favorite.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class CacheFavorite {
|
class CacheFavorite {
|
||||||
CacheFavorite(this._c) : assert(require(_c));
|
const CacheFavorite(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// Cache favorites using results from remote
|
/// Cache favorites using results from remote
|
||||||
///
|
///
|
||||||
/// Return number of files updated
|
/// Return number of files updated
|
||||||
Future<int> call(Account account, Iterable<int> remoteFileIds) async {
|
Future<int> call(Account account, Iterable<int> remoteFileIds) async {
|
||||||
_log.info("[call] Cache favorites");
|
_log.info("[call] Cache favorites");
|
||||||
final remote = remoteFileIds.sorted(Comparable.compare);
|
final result = await _c.npDb.syncFavoriteFiles(
|
||||||
final updateCount = await _c.sqliteDb.use((db) async {
|
account: account.toDb(),
|
||||||
final dbAccount = await db.accountOf(account);
|
favoriteFileIds: remoteFileIds.toList(),
|
||||||
final cache = await _getCacheFavorites(db, dbAccount);
|
);
|
||||||
final cacheMap =
|
final count = result.insert + result.delete + result.update;
|
||||||
Map.fromEntries(cache.map((e) => MapEntry(e.fileId, e.rowId)));
|
if (count > 0) {
|
||||||
final diff = getDiff(cacheMap.keys.sorted(Comparable.compare), remote);
|
|
||||||
final newFileIds = diff.onlyInB;
|
|
||||||
_log.info("[call] New favorites: ${newFileIds.toReadableString()}");
|
|
||||||
final removedFildIds = diff.onlyInA;
|
|
||||||
_log.info(
|
|
||||||
"[call] Removed favorites: ${removedFildIds.toReadableString()}");
|
|
||||||
|
|
||||||
var updateCount = 0;
|
|
||||||
if (newFileIds.isNotEmpty) {
|
|
||||||
final rowIds = await db.accountFileRowIdsByFileIds(
|
|
||||||
sql.ByAccount.sql(dbAccount), newFileIds);
|
|
||||||
final counts =
|
|
||||||
await rowIds.map((id) => id.accountFileRowId).withPartition(
|
|
||||||
(sublist) async {
|
|
||||||
return [
|
|
||||||
await (db.update(db.accountFiles)
|
|
||||||
..where((t) => t.rowId.isIn(sublist)))
|
|
||||||
.write(const sql.AccountFilesCompanion(
|
|
||||||
isFavorite: sql.Value(true))),
|
|
||||||
];
|
|
||||||
},
|
|
||||||
sql.maxByFileIdsSize,
|
|
||||||
);
|
|
||||||
final count = counts.sum;
|
|
||||||
_log.info("[call] Updated $count row (new)");
|
|
||||||
updateCount += count;
|
|
||||||
}
|
|
||||||
if (removedFildIds.isNotEmpty) {
|
|
||||||
final counts =
|
|
||||||
await removedFildIds.map((id) => cacheMap[id]!).withPartition(
|
|
||||||
(sublist) async {
|
|
||||||
return [
|
|
||||||
await (db.update(db.accountFiles)
|
|
||||||
..where((t) =>
|
|
||||||
t.account.equals(dbAccount.rowId) &
|
|
||||||
t.file.isIn(sublist)))
|
|
||||||
.write(const sql.AccountFilesCompanion(
|
|
||||||
isFavorite: sql.Value(false)))
|
|
||||||
];
|
|
||||||
},
|
|
||||||
sql.maxByFileIdsSize,
|
|
||||||
);
|
|
||||||
final count = counts.sum;
|
|
||||||
_log.info("[call] Updated $count row (remove)");
|
|
||||||
updateCount += count;
|
|
||||||
}
|
|
||||||
return updateCount;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (updateCount > 0) {
|
|
||||||
KiwiContainer().resolve<EventBus>().fire(FavoriteResyncedEvent(account));
|
KiwiContainer().resolve<EventBus>().fire(FavoriteResyncedEvent(account));
|
||||||
}
|
}
|
||||||
return updateCount;
|
return count;
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<_FileRowIdWithFileId>> _getCacheFavorites(
|
|
||||||
sql.SqliteDb db, sql.Account dbAccount) async {
|
|
||||||
final query = db.queryFiles().run((q) {
|
|
||||||
q
|
|
||||||
..setQueryMode(sql.FilesQueryMode.expression,
|
|
||||||
expressions: [db.files.rowId, db.files.fileId])
|
|
||||||
..setSqlAccount(dbAccount)
|
|
||||||
..byFavorite(true);
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
return await query
|
|
||||||
.map((r) => _FileRowIdWithFileId(
|
|
||||||
r.read(db.files.rowId)!, r.read(db.files.fileId)!))
|
|
||||||
.get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FileRowIdWithFileId {
|
|
||||||
const _FileRowIdWithFileId(this.rowId, this.fileId);
|
|
||||||
|
|
||||||
final int rowId;
|
|
||||||
final int fileId;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,21 +1,17 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
|
||||||
part 'v46.g.dart';
|
part 'v46.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class CompatV46 {
|
class CompatV46 {
|
||||||
static Future<void> insertDbAccounts(Pref pref, sql.SqliteDb sqliteDb) async {
|
static Future<void> insertDbAccounts(Pref pref, NpDb db) async {
|
||||||
_log.info("[insertDbAccounts] Insert current accounts to Sqlite database");
|
_log.info("[insertDbAccounts] Insert current accounts to Sqlite database");
|
||||||
await sqliteDb.use((db) async {
|
final accounts = pref.getAccounts3Or([]);
|
||||||
final accounts = pref.getAccounts3Or([]);
|
await db.addAccounts(accounts.toDb());
|
||||||
for (final a in accounts) {
|
|
||||||
_log.info("[insertDbAccounts] Insert account to Sqlite db: $a");
|
|
||||||
await db.insertAccountOf(a);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final _log = _$CompatV46NpLog.log;
|
static final _log = _$CompatV46NpLog.log;
|
||||||
|
|
|
@ -1,100 +1,10 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:logging/logging.dart';
|
|
||||||
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
|
||||||
import 'package:np_collection/np_collection.dart';
|
|
||||||
import 'package:tuple/tuple.dart';
|
|
||||||
|
|
||||||
part 'v55.g.dart';
|
|
||||||
|
|
||||||
@npLog
|
|
||||||
class CompatV55 {
|
class CompatV55 {
|
||||||
static Future<void> migrateDb(
|
static Future<void> migrateDb(
|
||||||
sql.SqliteDb db, {
|
NpDb db, {
|
||||||
void Function(int current, int count)? onProgress,
|
void Function(int current, int count)? onProgress,
|
||||||
}) {
|
}) {
|
||||||
return db.use((db) async {
|
return db.migrateV55(onProgress);
|
||||||
final countExp = db.accountFiles.rowId.count();
|
|
||||||
final countQ = db.selectOnly(db.accountFiles)..addColumns([countExp]);
|
|
||||||
final count = await countQ.map((r) => r.read(countExp)!).getSingle();
|
|
||||||
onProgress?.call(0, count);
|
|
||||||
|
|
||||||
final dateTimeUpdates = <Tuple2<int, DateTime>>[];
|
|
||||||
final imageRemoves = <int>[];
|
|
||||||
for (var i = 0; i < count; i += 1000) {
|
|
||||||
final q = db.select(db.files).join([
|
|
||||||
sql.innerJoin(
|
|
||||||
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
|
||||||
sql.innerJoin(db.images,
|
|
||||||
db.images.accountFile.equalsExp(db.accountFiles.rowId)),
|
|
||||||
]);
|
|
||||||
q
|
|
||||||
..orderBy([
|
|
||||||
sql.OrderingTerm(
|
|
||||||
expression: db.accountFiles.rowId,
|
|
||||||
mode: sql.OrderingMode.asc,
|
|
||||||
),
|
|
||||||
])
|
|
||||||
..limit(1000, offset: i);
|
|
||||||
final dbFiles = await q
|
|
||||||
.map((r) => sql.CompleteFile(
|
|
||||||
r.readTable(db.files),
|
|
||||||
r.readTable(db.accountFiles),
|
|
||||||
r.readTable(db.images),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
for (final f in dbFiles) {
|
|
||||||
final bestDateTime = file_util.getBestDateTime(
|
|
||||||
overrideDateTime: f.accountFile.overrideDateTime,
|
|
||||||
dateTimeOriginal: f.image?.dateTimeOriginal,
|
|
||||||
lastModified: f.file.lastModified,
|
|
||||||
);
|
|
||||||
if (f.accountFile.bestDateTime != bestDateTime) {
|
|
||||||
// need update
|
|
||||||
dateTimeUpdates.add(Tuple2(f.accountFile.rowId, bestDateTime));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (f.file.contentType == "image/heic" &&
|
|
||||||
f.image != null &&
|
|
||||||
f.image!.exifRaw == null) {
|
|
||||||
imageRemoves.add(f.accountFile.rowId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onProgress?.call(i, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
_log.info(
|
|
||||||
"[migrateDb] ${dateTimeUpdates.length} rows require updating, ${imageRemoves.length} rows require removing");
|
|
||||||
if (kDebugMode) {
|
|
||||||
_log.fine(
|
|
||||||
"[migrateDb] dateTimeUpdates: ${dateTimeUpdates.map((e) => e.item1).toReadableString()}");
|
|
||||||
_log.fine(
|
|
||||||
"[migrateDb] imageRemoves: ${imageRemoves.map((e) => e).toReadableString()}");
|
|
||||||
}
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final pair in dateTimeUpdates) {
|
|
||||||
batch.update(
|
|
||||||
db.accountFiles,
|
|
||||||
sql.AccountFilesCompanion(
|
|
||||||
bestDateTime: sql.Value(pair.item2),
|
|
||||||
),
|
|
||||||
where: (sql.$AccountFilesTable table) =>
|
|
||||||
table.rowId.equals(pair.item1),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final r in imageRemoves) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.images,
|
|
||||||
(sql.$ImagesTable table) => table.accountFile.equals(r),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static final _log = _$CompatV55NpLog.log;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,11 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_person.dart';
|
import 'package:nc_photos/entity/face_recognition_person.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/use_case/face_recognition_person/list_face_recognition_person.dart';
|
import 'package:nc_photos/use_case/face_recognition_person/list_face_recognition_person.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
|
||||||
|
|
||||||
part 'sync_face_recognition_person.g.dart';
|
part 'sync_face_recognition_person.g.dart';
|
||||||
|
|
||||||
|
@ -22,13 +18,10 @@ class SyncFaceRecognitionPerson {
|
||||||
/// Return if any people were updated
|
/// Return if any people were updated
|
||||||
Future<bool> call(Account account) async {
|
Future<bool> call(Account account) async {
|
||||||
_log.info("[call] Sync people with remote");
|
_log.info("[call] Sync people with remote");
|
||||||
int personSorter(FaceRecognitionPerson a, FaceRecognitionPerson b) =>
|
final List<FaceRecognitionPerson> remote;
|
||||||
a.name.compareTo(b.name);
|
|
||||||
late final List<FaceRecognitionPerson> remote;
|
|
||||||
try {
|
try {
|
||||||
remote = (await ListFaceRecognitionPerson(_c.withRemoteRepo())(account)
|
remote =
|
||||||
.last)
|
await ListFaceRecognitionPerson(_c.withRemoteRepo())(account).last;
|
||||||
..sort(personSorter);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is ApiException && e.response.statusCode == 404) {
|
if (e is ApiException && e.response.statusCode == 404) {
|
||||||
// face recognition app probably not installed, ignore
|
// face recognition app probably not installed, ignore
|
||||||
|
@ -37,56 +30,11 @@ class SyncFaceRecognitionPerson {
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
final cache = (await ListFaceRecognitionPerson(_c.withLocalRepo())(account)
|
final result = await _c.npDb.syncFaceRecognitionPersons(
|
||||||
.last)
|
account: account.toDb(),
|
||||||
..sort(personSorter);
|
persons: remote.map(DbFaceRecognitionPersonConverter.toDb).toList(),
|
||||||
final diff = getDiffWith(cache, remote, personSorter);
|
);
|
||||||
final inserts = diff.onlyInB;
|
return result.insert > 0 || result.delete > 0 || result.update > 0;
|
||||||
_log.info("[call] New people: ${inserts.toReadableString()}");
|
|
||||||
final deletes = diff.onlyInA;
|
|
||||||
_log.info("[call] Removed people: ${deletes.toReadableString()}");
|
|
||||||
final updates = remote.where((r) {
|
|
||||||
final c = cache.firstWhereOrNull((c) => c.name == r.name);
|
|
||||||
return c != null && c != r;
|
|
||||||
}).toList();
|
|
||||||
_log.info("[call] Updated people: ${updates.toReadableString()}");
|
|
||||||
|
|
||||||
if (inserts.isNotEmpty || deletes.isNotEmpty || updates.isNotEmpty) {
|
|
||||||
await _c.sqliteDb.use((db) async {
|
|
||||||
final dbAccount = await db.accountOf(account);
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final d in deletes) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.faceRecognitionPersons,
|
|
||||||
(sql.$FaceRecognitionPersonsTable p) =>
|
|
||||||
p.account.equals(dbAccount.rowId) & p.name.equals(d.name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final u in updates) {
|
|
||||||
batch.update(
|
|
||||||
db.faceRecognitionPersons,
|
|
||||||
sql.FaceRecognitionPersonsCompanion(
|
|
||||||
name: sql.Value(u.name),
|
|
||||||
thumbFaceId: sql.Value(u.thumbFaceId),
|
|
||||||
count: sql.Value(u.count),
|
|
||||||
),
|
|
||||||
where: (sql.$FaceRecognitionPersonsTable p) =>
|
|
||||||
p.account.equals(dbAccount.rowId) & p.name.equals(u.name),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final i in inserts) {
|
|
||||||
batch.insert(
|
|
||||||
db.faceRecognitionPersons,
|
|
||||||
SqliteFaceRecognitionPersonConverter.toSql(dbAccount, i),
|
|
||||||
mode: sql.InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.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/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@ part 'find_file.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class FindFile {
|
class FindFile {
|
||||||
FindFile(this._c) : assert(require(_c));
|
const FindFile(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// Find list of files in the DB by [fileIds]
|
/// Find list of files in the DB by [fileIds]
|
||||||
///
|
///
|
||||||
|
@ -24,10 +22,13 @@ class FindFile {
|
||||||
void Function(int fileId)? onFileNotFound,
|
void Function(int fileId)? onFileNotFound,
|
||||||
}) async {
|
}) async {
|
||||||
_log.info("[call] fileIds: ${fileIds.toReadableString()}");
|
_log.info("[call] fileIds: ${fileIds.toReadableString()}");
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final results = await _c.npDb.getFilesByFileIds(
|
||||||
return await db.completeFilesByFileIds(fileIds, appAccount: account);
|
account: account.toDb(),
|
||||||
});
|
fileIds: fileIds,
|
||||||
final files = await dbFiles.convertToAppFile(account);
|
);
|
||||||
|
final files = results
|
||||||
|
.map((e) => DbFileConverter.fromDb(account.userId.toString(), e))
|
||||||
|
.toList();
|
||||||
final fileMap = <int, File>{};
|
final fileMap = <int, File>{};
|
||||||
for (final f in files) {
|
for (final f in files) {
|
||||||
fileMap[f.fileId!] = f;
|
fileMap[f.fileId!] = f;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
|
||||||
|
@ -10,9 +10,7 @@ part 'find_file_descriptor.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class FindFileDescriptor {
|
class FindFileDescriptor {
|
||||||
FindFileDescriptor(this._c) : assert(require(_c));
|
const FindFileDescriptor(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// Find list of files in the DB by [fileIds]
|
/// Find list of files in the DB by [fileIds]
|
||||||
///
|
///
|
||||||
|
@ -24,11 +22,14 @@ class FindFileDescriptor {
|
||||||
void Function(int fileId)? onFileNotFound,
|
void Function(int fileId)? onFileNotFound,
|
||||||
}) async {
|
}) async {
|
||||||
_log.info("[call] fileIds: ${fileIds.toReadableString()}");
|
_log.info("[call] fileIds: ${fileIds.toReadableString()}");
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final dbResults = await _c.npDb.getFileDescriptors(
|
||||||
return await db.fileDescriptorsByFileIds(
|
account: account.toDb(),
|
||||||
sql.ByAccount.app(account), fileIds);
|
fileIds: fileIds,
|
||||||
});
|
);
|
||||||
final files = dbFiles.convertToAppFileDescriptor(account);
|
final files = dbResults
|
||||||
|
.map((e) =>
|
||||||
|
DbFileDescriptorConverter.fromDb(account.userId.toString(), e))
|
||||||
|
.toList();
|
||||||
final fileMap = <int, FileDescriptor>{};
|
final fileMap = <int, FileDescriptor>{};
|
||||||
for (final f in files) {
|
for (final f in files) {
|
||||||
fileMap[f.fdId] = f;
|
fileMap[f.fdId] = f;
|
||||||
|
|
|
@ -5,9 +5,7 @@ import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/use_case/find_file.dart';
|
import 'package:nc_photos/use_case/find_file.dart';
|
||||||
|
|
||||||
class InflateFileDescriptor {
|
class InflateFileDescriptor {
|
||||||
InflateFileDescriptor(this._c)
|
InflateFileDescriptor(this._c) : assert(require(_c));
|
||||||
: assert(require(_c)),
|
|
||||||
assert(FindFile.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => true;
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
|
|
|
@ -1,57 +1,23 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.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_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:np_geocoder/np_geocoder.dart';
|
|
||||||
|
|
||||||
class ListLocationFile {
|
class ListLocationFile {
|
||||||
ListLocationFile(this._c) : assert(require(_c));
|
const ListLocationFile(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// List all files located in [place], [countryCode]
|
/// List all files located in [place], [countryCode]
|
||||||
Future<List<File>> call(
|
Future<List<File>> call(
|
||||||
Account account, File dir, String? place, String countryCode) async {
|
Account account, File dir, String? place, String countryCode) async {
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final dbFiles = await _c.npDb.getFilesByDirKeyAndLocation(
|
||||||
final query = db.queryFiles().run((q) {
|
account: account.toDb(),
|
||||||
q
|
dirRelativePath: dir.strippedPathWithEmpty,
|
||||||
..setQueryMode(sql.FilesQueryMode.completeFile)
|
place: place,
|
||||||
..setAppAccount(account);
|
countryCode: countryCode,
|
||||||
dir.strippedPathWithEmpty.run((p) {
|
);
|
||||||
if (p.isNotEmpty) {
|
|
||||||
q.byOrRelativePathPattern("$p/%");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
if (place == null || alpha2CodeToName(countryCode) == place) {
|
|
||||||
// some places in the DB have the same name as the country, in such
|
|
||||||
// cases, we return all photos from the country
|
|
||||||
query.where(db.imageLocations.countryCode.equals(countryCode));
|
|
||||||
} else {
|
|
||||||
query
|
|
||||||
..where(db.imageLocations.name.equals(place) |
|
|
||||||
db.imageLocations.admin1.equals(place) |
|
|
||||||
db.imageLocations.admin2.equals(place))
|
|
||||||
..where(db.imageLocations.countryCode.equals(countryCode));
|
|
||||||
}
|
|
||||||
return await query
|
|
||||||
.map((r) => sql.CompleteFile(
|
|
||||||
r.readTable(db.files),
|
|
||||||
r.readTable(db.accountFiles),
|
|
||||||
r.readTableOrNull(db.images),
|
|
||||||
r.readTableOrNull(db.imageLocations),
|
|
||||||
r.readTableOrNull(db.trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
return dbFiles
|
return dbFiles
|
||||||
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
|
.map((f) => DbFileConverter.fromDb(account.userId.toString(), f))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_geocoder/np_geocoder.dart';
|
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'list_location_group.g.dart';
|
part 'list_location_group.g.dart';
|
||||||
|
@ -58,172 +57,29 @@ class LocationGroupResult with EquatableMixin {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class ListLocationGroup {
|
class ListLocationGroup {
|
||||||
ListLocationGroup(this._c) : assert(require(_c));
|
const ListLocationGroup(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
/// List location groups based on the name of the places
|
/// List location groups based on the name of the places
|
||||||
Future<LocationGroupResult> call(Account account) async {
|
Future<LocationGroupResult> call(Account account) async {
|
||||||
final s = Stopwatch()..start();
|
final s = Stopwatch()..start();
|
||||||
try {
|
try {
|
||||||
return await _c.sqliteDb.use((db) async {
|
final dbObj = await _c.npDb.groupLocations(
|
||||||
final dbAccount = await db.accountOf(account);
|
account: account.toDb(),
|
||||||
|
includeRelativeRoots: account.roots,
|
||||||
final nameResult = <LocationGroup>[];
|
excludeRelativeRoots: [
|
||||||
final admin1Result = <LocationGroup>[];
|
remote_storage_util.remoteStorageDirRelativePath
|
||||||
final admin2Result = <LocationGroup>[];
|
],
|
||||||
final countryCodeResult = <LocationGroup>[];
|
);
|
||||||
for (final r in account.roots) {
|
return LocationGroupResult(
|
||||||
final latest = db.accountFiles.bestDateTime.max();
|
dbObj.name.map(DbLocationGroupConverter.fromDb).toList(),
|
||||||
final count = db.imageLocations.rowId.count();
|
dbObj.admin1.map(DbLocationGroupConverter.fromDb).toList(),
|
||||||
final nameQ = _buildQuery(
|
dbObj.admin2.map(DbLocationGroupConverter.fromDb).toList(),
|
||||||
db, dbAccount, r, latest, count, db.imageLocations.name);
|
dbObj.countryCode.map(DbLocationGroupConverter.fromDb).toList(),
|
||||||
try {
|
);
|
||||||
_mergeResults(
|
|
||||||
nameResult,
|
|
||||||
await nameQ.map((r) {
|
|
||||||
return LocationGroup(
|
|
||||||
r.read(db.imageLocations.name)!,
|
|
||||||
r.read(db.imageLocations.countryCode)!,
|
|
||||||
r.read(count)!,
|
|
||||||
r.read(db.files.fileId)!,
|
|
||||||
r.read(latest)!.toUtc(),
|
|
||||||
);
|
|
||||||
}).get(),
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[call] Failed while query name group", e, stackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
final admin1Q = _buildQuery(
|
|
||||||
db, dbAccount, r, latest, count, db.imageLocations.admin1);
|
|
||||||
try {
|
|
||||||
_mergeResults(
|
|
||||||
admin1Result,
|
|
||||||
await admin1Q
|
|
||||||
.map((r) => LocationGroup(
|
|
||||||
r.read(db.imageLocations.admin1)!,
|
|
||||||
r.read(db.imageLocations.countryCode)!,
|
|
||||||
r.read(count)!,
|
|
||||||
r.read(db.files.fileId)!,
|
|
||||||
r.read(latest)!.toUtc(),
|
|
||||||
))
|
|
||||||
.get(),
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[call] Failed while query admin1 group", e, stackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
final admin2Q = _buildQuery(
|
|
||||||
db, dbAccount, r, latest, count, db.imageLocations.admin2);
|
|
||||||
try {
|
|
||||||
_mergeResults(
|
|
||||||
admin2Result,
|
|
||||||
await admin2Q
|
|
||||||
.map((r) => LocationGroup(
|
|
||||||
r.read(db.imageLocations.admin2)!,
|
|
||||||
r.read(db.imageLocations.countryCode)!,
|
|
||||||
r.read(count)!,
|
|
||||||
r.read(db.files.fileId)!,
|
|
||||||
r.read(latest)!.toUtc(),
|
|
||||||
))
|
|
||||||
.get(),
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[call] Failed while query admin2 group", e, stackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
final countryCodeQ = _buildQuery(
|
|
||||||
db, dbAccount, r, latest, count, db.imageLocations.countryCode);
|
|
||||||
try {
|
|
||||||
_mergeResults(
|
|
||||||
countryCodeResult,
|
|
||||||
await countryCodeQ.map((r) {
|
|
||||||
final cc = r.read(db.imageLocations.countryCode)!;
|
|
||||||
return LocationGroup(
|
|
||||||
alpha2CodeToName(cc) ?? cc,
|
|
||||||
cc,
|
|
||||||
r.read(count)!,
|
|
||||||
r.read(db.files.fileId)!,
|
|
||||||
r.read(latest)!.toUtc(),
|
|
||||||
);
|
|
||||||
}).get(),
|
|
||||||
);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout(
|
|
||||||
"[call] Failed while query countryCode group", e, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return LocationGroupResult(
|
|
||||||
nameResult, admin1Result, admin2Result, countryCodeResult);
|
|
||||||
});
|
|
||||||
} finally {
|
} finally {
|
||||||
_log.info("[call] Elapsed time: ${s.elapsedMilliseconds}ms");
|
_log.info("[call] Elapsed time: ${s.elapsedMilliseconds}ms");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sql.JoinedSelectStatement _buildQuery(
|
|
||||||
sql.SqliteDb db,
|
|
||||||
sql.Account dbAccount,
|
|
||||||
String dir,
|
|
||||||
sql.Expression<DateTime> latest,
|
|
||||||
sql.Expression<int> count,
|
|
||||||
sql.GeneratedColumn<String> groupColumn,
|
|
||||||
) {
|
|
||||||
final query = db.selectOnly(db.imageLocations).join([
|
|
||||||
sql.innerJoin(db.accountFiles,
|
|
||||||
db.accountFiles.rowId.equalsExp(db.imageLocations.accountFile),
|
|
||||||
useColumns: false),
|
|
||||||
sql.innerJoin(db.files, db.files.rowId.equalsExp(db.accountFiles.file),
|
|
||||||
useColumns: false),
|
|
||||||
]);
|
|
||||||
if (identical(groupColumn, db.imageLocations.countryCode)) {
|
|
||||||
query
|
|
||||||
..addColumns([
|
|
||||||
db.imageLocations.countryCode,
|
|
||||||
count,
|
|
||||||
db.files.fileId,
|
|
||||||
latest,
|
|
||||||
])
|
|
||||||
..groupBy([db.imageLocations.countryCode],
|
|
||||||
having: db.accountFiles.bestDateTime.equalsExp(latest));
|
|
||||||
} else {
|
|
||||||
query
|
|
||||||
..addColumns([
|
|
||||||
groupColumn,
|
|
||||||
db.imageLocations.countryCode,
|
|
||||||
count,
|
|
||||||
db.files.fileId,
|
|
||||||
latest,
|
|
||||||
])
|
|
||||||
..groupBy([groupColumn, db.imageLocations.countryCode],
|
|
||||||
having: db.accountFiles.bestDateTime.equalsExp(latest));
|
|
||||||
}
|
|
||||||
query
|
|
||||||
..where(db.accountFiles.account.equals(dbAccount.rowId))
|
|
||||||
..where(groupColumn.isNotNull());
|
|
||||||
if (dir.isNotEmpty) {
|
|
||||||
query.where(db.accountFiles.relativePath.like("$dir/%"));
|
|
||||||
}
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void _mergeResults(
|
|
||||||
List<LocationGroup> into, List<LocationGroup> from) {
|
|
||||||
for (final g in from) {
|
|
||||||
final i = into.indexWhere(
|
|
||||||
(e) => e.place == g.place && e.countryCode == g.countryCode);
|
|
||||||
if (i >= 0) {
|
|
||||||
// duplicate entry, sum the count and pick the newer file
|
|
||||||
final newer =
|
|
||||||
into[i].latestDateTime.isAfter(g.latestDateTime) ? into[i] : g;
|
|
||||||
into[i] = LocationGroup(g.place, g.countryCode, into[i].count + g.count,
|
|
||||||
newer.latestFileId, newer.latestDateTime);
|
|
||||||
} else {
|
|
||||||
into.add(g);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,9 +13,7 @@ part 'list_share.g.dart';
|
||||||
/// List all shares from a given file
|
/// List all shares from a given file
|
||||||
@npLog
|
@npLog
|
||||||
class ListShare {
|
class ListShare {
|
||||||
ListShare(this._c)
|
ListShare(this._c) : assert(require(_c));
|
||||||
: assert(require(_c)),
|
|
||||||
assert(FindFile.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.shareRepo);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.shareRepo);
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,7 @@ part 'list_tagged_file.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class ListTaggedFile {
|
class ListTaggedFile {
|
||||||
ListTaggedFile(this._c)
|
ListTaggedFile(this._c) : assert(require(_c));
|
||||||
: assert(require(_c)),
|
|
||||||
assert(FindFile.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) =>
|
static bool require(DiContainer c) =>
|
||||||
DiContainer.has(c, DiType.taggedFileRepo);
|
DiContainer.has(c, DiType.taggedFileRepo);
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/recognize_face.dart';
|
import 'package:nc_photos/entity/recognize_face.dart';
|
||||||
import 'package:nc_photos/entity/recognize_face_item.dart';
|
import 'package:nc_photos/entity/recognize_face_item.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/use_case/recognize_face/list_recognize_face.dart';
|
import 'package:nc_photos/use_case/recognize_face/list_recognize_face.dart';
|
||||||
import 'package:nc_photos/use_case/recognize_face/list_recognize_face_item.dart';
|
import 'package:nc_photos/use_case/recognize_face/list_recognize_face_item.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
|
||||||
|
|
||||||
part 'sync_recognize_face.g.dart';
|
part 'sync_recognize_face.g.dart';
|
||||||
|
|
||||||
|
@ -24,95 +20,28 @@ class SyncRecognizeFace {
|
||||||
/// Return if any people were updated
|
/// Return if any people were updated
|
||||||
Future<bool> call(Account account) async {
|
Future<bool> call(Account account) async {
|
||||||
_log.info("[call] Sync people with remote");
|
_log.info("[call] Sync people with remote");
|
||||||
final faces = await _getFaceResults(account);
|
final List<RecognizeFace> remote;
|
||||||
if (faces == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
var shouldUpdate = !faces.isEmpty;
|
|
||||||
final items =
|
|
||||||
await _getFaceItemResults(account, faces.results.values.toList());
|
|
||||||
shouldUpdate = shouldUpdate || items.values.any((e) => !e.isEmpty);
|
|
||||||
if (!shouldUpdate) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
await _c.sqliteDb.use((db) async {
|
|
||||||
final dbAccount = await db.accountOf(account);
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final d in faces.deletes) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.recognizeFaces,
|
|
||||||
(sql.$RecognizeFacesTable t) =>
|
|
||||||
t.account.equals(dbAccount.rowId) & t.label.equals(d),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final u in faces.updates) {
|
|
||||||
batch.update(
|
|
||||||
db.recognizeFaces,
|
|
||||||
sql.RecognizeFacesCompanion(
|
|
||||||
label: sql.Value(faces.results[u]!.label),
|
|
||||||
),
|
|
||||||
where: (sql.$RecognizeFacesTable t) =>
|
|
||||||
t.account.equals(dbAccount.rowId) & t.label.equals(u),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final i in faces.inserts) {
|
|
||||||
batch.insert(
|
|
||||||
db.recognizeFaces,
|
|
||||||
SqliteRecognizeFaceConverter.toSql(dbAccount, faces.results[i]!),
|
|
||||||
mode: sql.InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// update each item
|
|
||||||
for (final f in faces.results.values) {
|
|
||||||
try {
|
|
||||||
await _syncDbForFaceItem(db, dbAccount, f, items[f]!);
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[call] Failed to update db for face: $f", e, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<_FaceResult?> _getFaceResults(Account account) async {
|
|
||||||
int faceSorter(RecognizeFace a, RecognizeFace b) =>
|
|
||||||
a.label.compareTo(b.label);
|
|
||||||
late final List<RecognizeFace> remote;
|
|
||||||
try {
|
try {
|
||||||
remote = (await ListRecognizeFace(_c.withRemoteRepo())(account).last)
|
remote = await ListRecognizeFace(_c.withRemoteRepo())(account).last;
|
||||||
..sort(faceSorter);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is ApiException && e.response.statusCode == 404) {
|
if (e is ApiException && e.response.statusCode == 404) {
|
||||||
// recognize app probably not installed, ignore
|
// recognize app probably not installed, ignore
|
||||||
_log.info("[_getFaceResults] Recognize app not installed");
|
_log.info("[call] Recognize app not installed");
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
final cache = (await ListRecognizeFace(_c.withLocalRepo())(account).last)
|
final remoteItems = await _getFaceItems(account, remote);
|
||||||
..sort(faceSorter);
|
return _c.npDb.syncRecognizeFacesAndItems(
|
||||||
final diff = getDiffWith(cache, remote, faceSorter);
|
account: account.toDb(),
|
||||||
final inserts = diff.onlyInB;
|
data: remoteItems.map((key, value) => MapEntry(
|
||||||
_log.info("[_getFaceResults] New face: ${inserts.toReadableString()}");
|
key.toDb(),
|
||||||
final deletes = diff.onlyInA;
|
value.map(DbRecognizeFaceItemConverter.toDb).toList(),
|
||||||
_log.info("[_getFaceResults] Removed face: ${deletes.toReadableString()}");
|
)),
|
||||||
final updates = remote.where((r) {
|
|
||||||
final c = cache.firstWhereOrNull((c) => c.label == r.label);
|
|
||||||
return c != null && c != r;
|
|
||||||
}).toList();
|
|
||||||
_log.info("[_getFaceResults] Updated face: ${updates.toReadableString()}");
|
|
||||||
return _FaceResult(
|
|
||||||
results: remote.map((e) => MapEntry(e.label, e)).toMap(),
|
|
||||||
inserts: inserts.map((e) => e.label).toList(),
|
|
||||||
updates: updates.map((e) => e.label).toList(),
|
|
||||||
deletes: deletes.map((e) => e.label).toList(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<RecognizeFace, _FaceItemResult>> _getFaceItemResults(
|
Future<Map<RecognizeFace, List<RecognizeFaceItem>>> _getFaceItems(
|
||||||
Account account, List<RecognizeFace> faces) async {
|
Account account, List<RecognizeFace> faces) async {
|
||||||
Object? firstError;
|
Object? firstError;
|
||||||
StackTrace? firstStackTrace;
|
StackTrace? firstStackTrace;
|
||||||
|
@ -121,7 +50,7 @@ class SyncRecognizeFace {
|
||||||
faces,
|
faces,
|
||||||
onError: (f, e, stackTrace) {
|
onError: (f, e, stackTrace) {
|
||||||
_log.severe(
|
_log.severe(
|
||||||
"[_getFaceItemResults] Failed while listing remote face: $f",
|
"[_getFaceItems] Failed while listing remote face: $f",
|
||||||
e,
|
e,
|
||||||
stackTrace,
|
stackTrace,
|
||||||
);
|
);
|
||||||
|
@ -135,148 +64,8 @@ class SyncRecognizeFace {
|
||||||
Error.throwWithStackTrace(
|
Error.throwWithStackTrace(
|
||||||
firstError!, firstStackTrace ?? StackTrace.current);
|
firstError!, firstStackTrace ?? StackTrace.current);
|
||||||
}
|
}
|
||||||
final cache = await ListMultipleRecognizeFaceItem(_c.withLocalRepo())(
|
return remote;
|
||||||
account,
|
|
||||||
faces,
|
|
||||||
onError: (f, e, stackTrace) {
|
|
||||||
_log.severe("[_getFaceItemResults] Failed while listing cache face: $f",
|
|
||||||
e, stackTrace);
|
|
||||||
},
|
|
||||||
).last;
|
|
||||||
|
|
||||||
int itemSorter(RecognizeFaceItem a, RecognizeFaceItem b) =>
|
|
||||||
a.fileId.compareTo(b.fileId);
|
|
||||||
final results = <RecognizeFace, _FaceItemResult>{};
|
|
||||||
for (final f in faces) {
|
|
||||||
final thisCache = (cache[f] ?? [])..sort(itemSorter);
|
|
||||||
final thisRemote = (remote[f] ?? [])..sort(itemSorter);
|
|
||||||
final diff =
|
|
||||||
getDiffWith<RecognizeFaceItem>(thisCache, thisRemote, itemSorter);
|
|
||||||
final inserts = diff.onlyInB;
|
|
||||||
_log.info(
|
|
||||||
"[_getFaceItemResults] New item: ${inserts.toReadableString()}");
|
|
||||||
final deletes = diff.onlyInA;
|
|
||||||
_log.info(
|
|
||||||
"[_getFaceItemResults] Removed item: ${deletes.toReadableString()}");
|
|
||||||
final updates = thisRemote.where((r) {
|
|
||||||
final c = thisCache.firstWhereOrNull((c) => c.fileId == r.fileId);
|
|
||||||
return c != null && c != r;
|
|
||||||
}).toList();
|
|
||||||
_log.info(
|
|
||||||
"[_getFaceItemResults] Updated item: ${updates.toReadableString()}");
|
|
||||||
results[f] = _FaceItemResult(
|
|
||||||
results: thisRemote.map((e) => MapEntry(e.fileId, e)).toMap(),
|
|
||||||
inserts: inserts.map((e) => e.fileId).toList(),
|
|
||||||
updates: updates.map((e) => e.fileId).toList(),
|
|
||||||
deletes: deletes.map((e) => e.fileId).toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Future<_FaceItemResult?> _getFaceItemResults(
|
|
||||||
// Account account, RecognizeFace face) async {
|
|
||||||
// late final List<RecognizeFaceItem> remote;
|
|
||||||
// try {
|
|
||||||
// remote =
|
|
||||||
// await ListRecognizeFaceItem(_c.withRemoteRepo())(account, face).last;
|
|
||||||
// } catch (e) {
|
|
||||||
// if (e is ApiException && e.response.statusCode == 404) {
|
|
||||||
// // recognize app probably not installed, ignore
|
|
||||||
// _log.info("[_getFaceItemResults] Recognize app not installed");
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
// rethrow;
|
|
||||||
// }
|
|
||||||
// final cache =
|
|
||||||
// await ListRecognizeFaceItem(_c.withLocalRepo())(account, face).last;
|
|
||||||
// int itemSorter(RecognizeFaceItem a, RecognizeFaceItem b) =>
|
|
||||||
// a.fileId.compareTo(b.fileId);
|
|
||||||
// final diff = getDiffWith(cache, remote, itemSorter);
|
|
||||||
// final inserts = diff.onlyInB;
|
|
||||||
// _log.info("[_getFaceItemResults] New face: ${inserts.toReadableString()}");
|
|
||||||
// final deletes = diff.onlyInA;
|
|
||||||
// _log.info(
|
|
||||||
// "[_getFaceItemResults] Removed face: ${deletes.toReadableString()}");
|
|
||||||
// final updates = remote.where((r) {
|
|
||||||
// final c = cache.firstWhereOrNull((c) => c.fileId == r.fileId);
|
|
||||||
// return c != null && c != r;
|
|
||||||
// }).toList();
|
|
||||||
// _log.info(
|
|
||||||
// "[_getFaceItemResults] Updated face: ${updates.toReadableString()}");
|
|
||||||
// return _FaceItemResult(
|
|
||||||
// results: remote.map((e) => MapEntry(e.fileId, e)).toMap(),
|
|
||||||
// inserts: inserts.map((e) => e.fileId).toList(),
|
|
||||||
// updates: updates.map((e) => e.fileId).toList(),
|
|
||||||
// deletes: deletes.map((e) => e.fileId).toList(),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
Future<void> _syncDbForFaceItem(sql.SqliteDb db, sql.Account dbAccount,
|
|
||||||
RecognizeFace face, _FaceItemResult item) async {
|
|
||||||
await db.transaction(() async {
|
|
||||||
final dbFace = await db.recognizeFaceByLabel(
|
|
||||||
account: sql.ByAccount.sql(dbAccount),
|
|
||||||
label: face.label,
|
|
||||||
);
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final d in item.deletes) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.recognizeFaceItems,
|
|
||||||
(sql.$RecognizeFaceItemsTable t) =>
|
|
||||||
t.parent.equals(dbFace.rowId) & t.fileId.equals(d),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final u in item.updates) {
|
|
||||||
batch.update(
|
|
||||||
db.recognizeFaceItems,
|
|
||||||
SqliteRecognizeFaceItemConverter.toSql(dbFace, item.results[u]!),
|
|
||||||
where: (sql.$RecognizeFaceItemsTable t) =>
|
|
||||||
t.parent.equals(dbFace.rowId) & t.fileId.equals(u),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final i in item.inserts) {
|
|
||||||
batch.insert(
|
|
||||||
db.recognizeFaceItems,
|
|
||||||
SqliteRecognizeFaceItemConverter.toSql(dbFace, item.results[i]!),
|
|
||||||
mode: sql.InsertMode.insertOrIgnore,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FaceResult {
|
|
||||||
const _FaceResult({
|
|
||||||
required this.results,
|
|
||||||
required this.inserts,
|
|
||||||
required this.updates,
|
|
||||||
required this.deletes,
|
|
||||||
});
|
|
||||||
|
|
||||||
bool get isEmpty => inserts.isEmpty && updates.isEmpty && deletes.isEmpty;
|
|
||||||
|
|
||||||
final Map<String, RecognizeFace> results;
|
|
||||||
final List<String> inserts;
|
|
||||||
final List<String> updates;
|
|
||||||
final List<String> deletes;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FaceItemResult {
|
|
||||||
const _FaceItemResult({
|
|
||||||
required this.results,
|
|
||||||
required this.inserts,
|
|
||||||
required this.updates,
|
|
||||||
required this.deletes,
|
|
||||||
});
|
|
||||||
|
|
||||||
bool get isEmpty => inserts.isEmpty && updates.isEmpty && deletes.isEmpty;
|
|
||||||
|
|
||||||
final Map<int, RecognizeFaceItem> results;
|
|
||||||
final List<int> inserts;
|
|
||||||
final List<int> updates;
|
|
||||||
final List<int> deletes;
|
|
||||||
}
|
|
||||||
|
|
|
@ -14,9 +14,7 @@ part 'resync_album.g.dart';
|
||||||
/// Resync files inside an album with the file db
|
/// Resync files inside an album with the file db
|
||||||
@npLog
|
@npLog
|
||||||
class ResyncAlbum {
|
class ResyncAlbum {
|
||||||
ResyncAlbum(this._c)
|
ResyncAlbum(this._c) : assert(require(_c));
|
||||||
: assert(require(_c)),
|
|
||||||
assert(FindFile.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => true;
|
static bool require(DiContainer c) => true;
|
||||||
|
|
||||||
|
|
|
@ -1,132 +1,53 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.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_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
|
|
||||||
class ScanDirOffline {
|
class ScanDirOffline {
|
||||||
ScanDirOffline(this._c) : assert(require(_c));
|
const ScanDirOffline(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
|
||||||
|
|
||||||
Future<List<FileDescriptor>> call(
|
Future<List<FileDescriptor>> call(
|
||||||
Account account,
|
Account account,
|
||||||
File root, {
|
File root, {
|
||||||
bool isOnlySupportedFormat = true,
|
bool isOnlySupportedFormat = true,
|
||||||
}) async {
|
}) async {
|
||||||
return await _c.sqliteDb.isolate({
|
final results = await _c.npDb.getFileDescriptors(
|
||||||
"account": account,
|
account: account.toDb(),
|
||||||
"root": root,
|
includeRelativeRoots: [root.strippedPathWithEmpty],
|
||||||
"isOnlySupportedFormat": isOnlySupportedFormat,
|
excludeRelativeRoots: [remote_storage_util.remoteStorageDirRelativePath],
|
||||||
}, (db, Map args) async {
|
mimes: isOnlySupportedFormat ? file_util.supportedFormatMimes : null,
|
||||||
final Account account = args["account"];
|
);
|
||||||
final File root = args["root"];
|
return results
|
||||||
final strippedPath = root.strippedPathWithEmpty;
|
.map((e) =>
|
||||||
final bool isOnlySupportedFormat = args["isOnlySupportedFormat"];
|
DbFileDescriptorConverter.fromDb(account.userId.toString(), e))
|
||||||
final dbFiles = await db.useInIsolate((db) async {
|
.toList();
|
||||||
final query = db.queryFiles().run((q) {
|
|
||||||
q
|
|
||||||
..setQueryMode(
|
|
||||||
sql.FilesQueryMode.expression,
|
|
||||||
expressions: [
|
|
||||||
db.accountFiles.relativePath,
|
|
||||||
db.files.fileId,
|
|
||||||
db.files.contentType,
|
|
||||||
db.accountFiles.isArchived,
|
|
||||||
db.accountFiles.isFavorite,
|
|
||||||
db.accountFiles.bestDateTime,
|
|
||||||
],
|
|
||||||
)
|
|
||||||
..setAppAccount(account);
|
|
||||||
if (strippedPath.isNotEmpty) {
|
|
||||||
q.byOrRelativePathPattern("$strippedPath/%");
|
|
||||||
}
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
if (isOnlySupportedFormat) {
|
|
||||||
query.where(db.whereFileIsSupportedMime());
|
|
||||||
}
|
|
||||||
if (strippedPath.isEmpty) {
|
|
||||||
query.where(db.accountFiles.relativePath
|
|
||||||
.like("${remote_storage_util.remoteStorageDirRelativePath}/%")
|
|
||||||
.not());
|
|
||||||
}
|
|
||||||
return await query
|
|
||||||
.map((r) => <String, dynamic>{
|
|
||||||
"relativePath": r.read(db.accountFiles.relativePath)!,
|
|
||||||
"fileId": r.read(db.files.fileId)!,
|
|
||||||
"contentType": r.read(db.files.contentType)!,
|
|
||||||
"isArchived": r.read(db.accountFiles.isArchived),
|
|
||||||
"isFavorite": r.read(db.accountFiles.isFavorite),
|
|
||||||
"bestDateTime": r.read(db.accountFiles.bestDateTime)!.toUtc(),
|
|
||||||
})
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
return dbFiles
|
|
||||||
.map((f) => FileDescriptor(
|
|
||||||
fdPath:
|
|
||||||
"remote.php/dav/files/${account.userId.toString()}/${f["relativePath"]}",
|
|
||||||
fdId: f["fileId"],
|
|
||||||
fdMime: f["contentType"],
|
|
||||||
fdIsArchived: f["isArchived"] ?? false,
|
|
||||||
fdIsFavorite: f["isFavorite"] ?? false,
|
|
||||||
fdDateTime: f["bestDateTime"],
|
|
||||||
))
|
|
||||||
.toList();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScanDirOfflineMini {
|
class ScanDirOfflineMini {
|
||||||
ScanDirOfflineMini(this._c) : assert(require(_c));
|
const ScanDirOfflineMini(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.sqliteDb);
|
Future<List<FileDescriptor>> call(
|
||||||
|
|
||||||
Future<List<File>> call(
|
|
||||||
Account account,
|
Account account,
|
||||||
Iterable<File> roots,
|
List<File> roots,
|
||||||
int limit, {
|
int limit, {
|
||||||
bool isOnlySupportedFormat = true,
|
bool isOnlySupportedFormat = true,
|
||||||
}) async {
|
}) async {
|
||||||
final dbFiles = await _c.sqliteDb.use((db) async {
|
final results = await _c.npDb.getFileDescriptors(
|
||||||
final query = db.queryFiles().run((q) {
|
account: account.toDb(),
|
||||||
q
|
includeRelativeRoots: roots.map((e) => e.strippedPathWithEmpty).toList(),
|
||||||
..setQueryMode(sql.FilesQueryMode.completeFile)
|
excludeRelativeRoots: [remote_storage_util.remoteStorageDirRelativePath],
|
||||||
..setAppAccount(account);
|
mimes: isOnlySupportedFormat ? file_util.supportedFormatMimes : null,
|
||||||
for (final r in roots) {
|
limit: limit,
|
||||||
final path = r.strippedPathWithEmpty;
|
);
|
||||||
if (path.isEmpty) {
|
return results
|
||||||
break;
|
.map((e) =>
|
||||||
}
|
DbFileDescriptorConverter.fromDb(account.userId.toString(), e))
|
||||||
q.byOrRelativePathPattern("$path/%");
|
|
||||||
}
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
if (isOnlySupportedFormat) {
|
|
||||||
query.where(db.whereFileIsSupportedMime());
|
|
||||||
}
|
|
||||||
query
|
|
||||||
..orderBy([sql.OrderingTerm.desc(db.accountFiles.bestDateTime)])
|
|
||||||
..limit(limit);
|
|
||||||
return await query
|
|
||||||
.map((r) => sql.CompleteFile(
|
|
||||||
r.readTable(db.files),
|
|
||||||
r.readTable(db.accountFiles),
|
|
||||||
r.readTableOrNull(db.images),
|
|
||||||
r.readTableOrNull(db.imageLocations),
|
|
||||||
r.readTableOrNull(db.trashes),
|
|
||||||
))
|
|
||||||
.get();
|
|
||||||
});
|
|
||||||
return dbFiles
|
|
||||||
.map((f) => SqliteFileConverter.fromSql(account.userId.toString(), f))
|
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.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_descriptor.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/entity/search.dart';
|
import 'package:nc_photos/entity/search.dart';
|
||||||
|
|
||||||
|
@ -9,7 +9,8 @@ class Search {
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.searchRepo);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.searchRepo);
|
||||||
|
|
||||||
Future<List<File>> call(Account account, SearchCriteria criteria) async {
|
Future<List<FileDescriptor>> call(
|
||||||
|
Account account, SearchCriteria criteria) async {
|
||||||
final files = await _c.searchRepo.list(account, criteria);
|
final files = await _c.searchRepo.list(account, criteria);
|
||||||
return files.where((f) => file_util.isSupportedFormat(f)).toList();
|
return files.where((f) => file_util.isSupportedFormat(f)).toList();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,8 +24,7 @@ part 'startup_sync.g.dart';
|
||||||
class StartupSync {
|
class StartupSync {
|
||||||
StartupSync(this._c) : assert(require(_c));
|
StartupSync(this._c) : assert(require(_c));
|
||||||
|
|
||||||
static bool require(DiContainer c) =>
|
static bool require(DiContainer c) => SyncFavorite.require(c);
|
||||||
SyncFavorite.require(c) && SyncTag.require(c);
|
|
||||||
|
|
||||||
/// Sync in a background isolate
|
/// Sync in a background isolate
|
||||||
static Future<SyncResult> runInIsolate(
|
static Future<SyncResult> runInIsolate(
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.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/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/files_query_builder.dart' as sql;
|
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:nc_photos/progress_util.dart';
|
import 'package:nc_photos/progress_util.dart';
|
||||||
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
import 'package:nc_photos/remote_storage_util.dart' as remote_storage_util;
|
||||||
import 'package:nc_photos/use_case/ls_single_file.dart';
|
import 'package:nc_photos/use_case/ls_single_file.dart';
|
||||||
|
@ -23,7 +21,6 @@ class SyncDir {
|
||||||
|
|
||||||
static bool require(DiContainer c) =>
|
static bool require(DiContainer c) =>
|
||||||
DiContainer.has(c, DiType.fileRepoRemote) &&
|
DiContainer.has(c, DiType.fileRepoRemote) &&
|
||||||
DiContainer.has(c, DiType.sqliteDb) &&
|
|
||||||
DiContainer.has(c, DiType.touchManager);
|
DiContainer.has(c, DiType.touchManager);
|
||||||
|
|
||||||
/// Sync local SQLite DB with remote content
|
/// Sync local SQLite DB with remote content
|
||||||
|
@ -118,25 +115,10 @@ class SyncDir {
|
||||||
Future<Map<int, String>> _queryAllDirEtags(
|
Future<Map<int, String>> _queryAllDirEtags(
|
||||||
Account account, String dirPath) async {
|
Account account, String dirPath) async {
|
||||||
final dir = File(path: dirPath);
|
final dir = File(path: dirPath);
|
||||||
return await _c.sqliteDb.use((db) async {
|
return _c.npDb.getDirFileIdToEtagByLikeRelativePath(
|
||||||
final query = db.queryFiles().run((q) {
|
account: account.toDb(),
|
||||||
q
|
relativePath: dir.strippedPathWithEmpty,
|
||||||
..setQueryMode(sql.FilesQueryMode.expression,
|
);
|
||||||
expressions: [db.files.fileId, db.files.etag])
|
|
||||||
..setAppAccount(account);
|
|
||||||
if (dir.strippedPathWithEmpty.isNotEmpty) {
|
|
||||||
q
|
|
||||||
..byOrRelativePath(dir.strippedPathWithEmpty)
|
|
||||||
..byOrRelativePathPattern("${dir.strippedPathWithEmpty}/%");
|
|
||||||
}
|
|
||||||
return q.build();
|
|
||||||
});
|
|
||||||
query.where(db.files.isCollection.equals(true));
|
|
||||||
return Map.fromEntries(await query
|
|
||||||
.map(
|
|
||||||
(r) => MapEntry(r.read(db.files.fileId)!, r.read(db.files.etag)!))
|
|
||||||
.get());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
|
@ -11,9 +11,7 @@ part 'sync_favorite.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class SyncFavorite {
|
class SyncFavorite {
|
||||||
SyncFavorite(this._c)
|
SyncFavorite(this._c) : assert(require(_c));
|
||||||
: assert(require(_c)),
|
|
||||||
assert(CacheFavorite.require(_c));
|
|
||||||
|
|
||||||
static bool require(DiContainer c) => DiContainer.has(c, DiType.favoriteRepo);
|
static bool require(DiContainer c) => DiContainer.has(c, DiType.favoriteRepo);
|
||||||
|
|
||||||
|
|
|
@ -1,71 +1,23 @@
|
||||||
import 'package:collection/collection.dart';
|
|
||||||
import 'package:drift/drift.dart' as sql;
|
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
|
||||||
|
|
||||||
part 'sync_tag.g.dart';
|
part 'sync_tag.g.dart';
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class SyncTag {
|
class SyncTag {
|
||||||
SyncTag(this._c) : assert(require(_c));
|
const SyncTag(this._c);
|
||||||
|
|
||||||
static bool require(DiContainer c) =>
|
|
||||||
DiContainer.has(c, DiType.tagRepoRemote) &&
|
|
||||||
DiContainer.has(c, DiType.tagRepoLocal);
|
|
||||||
|
|
||||||
/// Sync tags in cache db with remote server
|
/// Sync tags in cache db with remote server
|
||||||
Future<void> call(Account account) async {
|
Future<void> call(Account account) async {
|
||||||
_log.info("[call] Sync tags with remote");
|
_log.info("[call] Sync tags with remote");
|
||||||
int tagSorter(Tag a, Tag b) => a.id.compareTo(b.id);
|
final remote = await _c.tagRepoRemote.list(account);
|
||||||
final remote = (await _c.tagRepoRemote.list(account))..sort(tagSorter);
|
await _c.npDb.syncTags(
|
||||||
final cache = (await _c.tagRepoLocal.list(account))..sort(tagSorter);
|
account: account.toDb(),
|
||||||
final diff = getDiffWith<Tag>(cache, remote, tagSorter);
|
tags: remote.map(DbTagConverter.toDb).toList(),
|
||||||
final inserts = diff.onlyInB;
|
);
|
||||||
_log.info("[call] New tags: ${inserts.toReadableString()}");
|
|
||||||
final deletes = diff.onlyInA;
|
|
||||||
_log.info("[call] Removed tags: ${deletes.toReadableString()}");
|
|
||||||
final updates = remote.where((r) {
|
|
||||||
final c = cache.firstWhereOrNull((c) => c.id == r.id);
|
|
||||||
return c != null && c != r;
|
|
||||||
}).toList();
|
|
||||||
_log.info("[call] Updated tags: ${updates.toReadableString()}");
|
|
||||||
|
|
||||||
if (inserts.isNotEmpty || deletes.isNotEmpty || updates.isNotEmpty) {
|
|
||||||
await _c.sqliteDb.use((db) async {
|
|
||||||
final dbAccount = await db.accountOf(account);
|
|
||||||
await db.batch((batch) {
|
|
||||||
for (final d in deletes) {
|
|
||||||
batch.deleteWhere(
|
|
||||||
db.tags,
|
|
||||||
(sql.$TagsTable t) =>
|
|
||||||
t.server.equals(dbAccount.server) & t.tagId.equals(d.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final u in updates) {
|
|
||||||
batch.update(
|
|
||||||
db.tags,
|
|
||||||
sql.TagsCompanion(
|
|
||||||
displayName: sql.Value(u.displayName),
|
|
||||||
userVisible: sql.Value(u.userVisible),
|
|
||||||
userAssignable: sql.Value(u.userAssignable),
|
|
||||||
),
|
|
||||||
where: (sql.$TagsTable t) =>
|
|
||||||
t.server.equals(dbAccount.server) & t.tagId.equals(u.id),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (final i in inserts) {
|
|
||||||
batch.insert(db.tags, SqliteTagConverter.toSql(dbAccount, i),
|
|
||||||
mode: sql.InsertMode.insertOrIgnore);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export 'db_util.dart';
|
|
||||||
export 'download.dart';
|
export 'download.dart';
|
||||||
export 'file_saver.dart';
|
export 'file_saver.dart';
|
||||||
export 'notification.dart';
|
export 'notification.dart';
|
||||||
|
|
|
@ -15,10 +15,10 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc_util.dart';
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
import 'package:nc_photos/controller/account_controller.dart';
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
import 'package:nc_photos/controller/pref_controller.dart';
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/server_status.dart';
|
import 'package:nc_photos/entity/server_status.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/exception_event.dart';
|
import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/help_utils.dart' as help_util;
|
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||||
|
@ -32,6 +32,7 @@ import 'package:nc_photos/widget/settings.dart';
|
||||||
import 'package:nc_photos/widget/settings/account_settings.dart';
|
import 'package:nc_photos/widget/settings/account_settings.dart';
|
||||||
import 'package:nc_photos/widget/sign_in.dart';
|
import 'package:nc_photos/widget/sign_in.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'account_picker_dialog.g.dart';
|
part 'account_picker_dialog.g.dart';
|
||||||
|
@ -51,6 +52,7 @@ class AccountPickerDialog extends StatelessWidget {
|
||||||
container: KiwiContainer().resolve(),
|
container: KiwiContainer().resolve(),
|
||||||
accountController: context.read(),
|
accountController: context.read(),
|
||||||
prefController: context.read(),
|
prefController: context.read(),
|
||||||
|
db: context.read(),
|
||||||
),
|
),
|
||||||
child: const _WrappedAccountPickerDialog(),
|
child: const _WrappedAccountPickerDialog(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
required DiContainer container,
|
required DiContainer container,
|
||||||
required this.accountController,
|
required this.accountController,
|
||||||
required this.prefController,
|
required this.prefController,
|
||||||
|
required this.db,
|
||||||
}) : _c = container,
|
}) : _c = container,
|
||||||
super(_State.init(
|
super(_State.init(
|
||||||
accounts: container.pref.getAccounts3Or([]),
|
accounts: container.pref.getAccounts3Or([]),
|
||||||
|
@ -100,9 +101,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
|
||||||
Future<void> _removeAccountFromDb(Account account) async {
|
Future<void> _removeAccountFromDb(Account account) async {
|
||||||
try {
|
try {
|
||||||
await _c.sqliteDb.use((db) async {
|
await db.deleteAccount(account.toDb());
|
||||||
await db.deleteAccountOf(account);
|
|
||||||
});
|
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[_removeAccountFromDb] Failed while removing account from db",
|
_log.shout("[_removeAccountFromDb] Failed while removing account from db",
|
||||||
e, stackTrace);
|
e, stackTrace);
|
||||||
|
@ -112,6 +111,7 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
final AccountController accountController;
|
final AccountController accountController;
|
||||||
final PrefController prefController;
|
final PrefController prefController;
|
||||||
|
final NpDb db;
|
||||||
late final Account activeAccount = accountController.account;
|
late final Account activeAccount = accountController.account;
|
||||||
|
|
||||||
final _prefLock = Mutex();
|
final _prefLock = Mutex();
|
||||||
|
|
|
@ -15,12 +15,13 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/progress.dart';
|
import 'package:nc_photos/bloc/progress.dart';
|
||||||
import 'package:nc_photos/bloc/scan_account_dir.dart';
|
import 'package:nc_photos/bloc/scan_account_dir.dart';
|
||||||
import 'package:nc_photos/controller/account_controller.dart';
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/collection.dart';
|
import 'package:nc_photos/entity/collection.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
|
import 'package:nc_photos/entity/file_util.dart' as file_util;
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
@ -534,12 +535,12 @@ class _HomePhotosState extends State<HomePhotos>
|
||||||
.value)) {
|
.value)) {
|
||||||
try {
|
try {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
final missingMetadataCount = await c.sqliteDb.use((db) async {
|
final missingMetadataCount =
|
||||||
return await db.countMissingMetadataByFileIds(
|
await c.npDb.countFilesByFileIdsMissingMetadata(
|
||||||
appAccount: widget.account,
|
account: widget.account.toDb(),
|
||||||
fileIds: _backingFiles.map((e) => e.fdId).toList(),
|
fileIds: _backingFiles.map((e) => e.fdId).toList(),
|
||||||
);
|
mimes: file_util.supportedImageFormatMimes,
|
||||||
});
|
);
|
||||||
_log.info(
|
_log.info(
|
||||||
"[_tryStartMetadataTask] Missing count: $missingMetadataCount");
|
"[_tryStartMetadataTask] Missing count: $missingMetadataCount");
|
||||||
if (missingMetadataCount > 0) {
|
if (missingMetadataCount > 0) {
|
||||||
|
|
|
@ -10,7 +10,6 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/search.dart';
|
import 'package:nc_photos/bloc/search.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/download_handler.dart';
|
import 'package:nc_photos/download_handler.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/search.dart';
|
import 'package:nc_photos/entity/search.dart';
|
||||||
|
@ -509,7 +508,7 @@ class _HomeSearchState extends State<HomeSearch>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _transformItems(List<File> files) {
|
void _transformItems(List<FileDescriptor> files) {
|
||||||
_buildItemQueue.addJob(
|
_buildItemQueue.addJob(
|
||||||
PhotoListItemBuilderArguments(
|
PhotoListItemBuilderArguments(
|
||||||
widget.account,
|
widget.account,
|
||||||
|
|
|
@ -50,6 +50,7 @@ import 'package:nc_photos/widget/trashbin_browser.dart';
|
||||||
import 'package:nc_photos/widget/trashbin_viewer.dart';
|
import 'package:nc_photos/widget/trashbin_viewer.dart';
|
||||||
import 'package:nc_photos/widget/viewer.dart';
|
import 'package:nc_photos/widget/viewer.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'my_app.g.dart';
|
part 'my_app.g.dart';
|
||||||
|
@ -74,6 +75,9 @@ class MyApp extends StatelessWidget {
|
||||||
RepositoryProvider(
|
RepositoryProvider(
|
||||||
create: (_) => PrefController(_c),
|
create: (_) => PrefController(_c),
|
||||||
),
|
),
|
||||||
|
RepositoryProvider<NpDb>(
|
||||||
|
create: (_) => _c.npDb,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: BlocProvider(
|
child: BlocProvider(
|
||||||
create: (context) => _Bloc(
|
create: (context) => _Bloc(
|
||||||
|
|
|
@ -40,15 +40,13 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
|
|
||||||
Future<void> _onVacuumDb(_VacuumDb ev, Emitter<_State> emit) async {
|
Future<void> _onVacuumDb(_VacuumDb ev, Emitter<_State> emit) async {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
await _c.sqliteDb.useNoTransaction((db) async {
|
await _c.npDb.sqlVacuum();
|
||||||
await db.customStatement("VACUUM;");
|
|
||||||
});
|
|
||||||
emit(state.copyWith(message: StateMessage("Finished successfully")));
|
emit(state.copyWith(message: StateMessage("Finished successfully")));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onExportDb(_ExportDb ev, Emitter<_State> emit) async {
|
Future<void> _onExportDb(_ExportDb ev, Emitter<_State> emit) async {
|
||||||
_log.info(ev);
|
_log.info(ev);
|
||||||
await platform.exportSqliteDb(_c.sqliteDb);
|
await _c.npDb.export(await getApplicationDocumentsDirectory());
|
||||||
emit(state.copyWith(message: StateMessage("Finished successfully")));
|
emit(state.copyWith(message: StateMessage("Finished successfully")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,15 @@ import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/bloc_util.dart';
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
import 'package:nc_photos/cache_manager_util.dart';
|
import 'package:nc_photos/cache_manager_util.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart';
|
|
||||||
import 'package:nc_photos/exception_event.dart';
|
import 'package:nc_photos/exception_event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/platform.dart'
|
|
||||||
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
|
|
||||||
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
import 'package:nc_photos/mobile/self_signed_cert_manager.dart';
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'developer/bloc.dart';
|
part 'developer/bloc.dart';
|
||||||
|
|
|
@ -10,8 +10,10 @@ class _Error {
|
||||||
|
|
||||||
@npLog
|
@npLog
|
||||||
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
_Bloc(DiContainer c)
|
_Bloc(
|
||||||
: _c = c,
|
DiContainer c, {
|
||||||
|
required this.db,
|
||||||
|
}) : _c = c,
|
||||||
super(const _State()) {
|
super(const _State()) {
|
||||||
on<_ClearCacheDatabase>(_onClearCacheDatabase);
|
on<_ClearCacheDatabase>(_onClearCacheDatabase);
|
||||||
}
|
}
|
||||||
|
@ -24,13 +26,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
Future<void> _onClearCacheDatabase(
|
Future<void> _onClearCacheDatabase(
|
||||||
_ClearCacheDatabase ev, Emitter<_State> emit) async {
|
_ClearCacheDatabase ev, Emitter<_State> emit) async {
|
||||||
try {
|
try {
|
||||||
await _c.sqliteDb.use((db) async {
|
final accounts = _c.pref.getAccounts3Or([]);
|
||||||
await db.truncate();
|
await db.clearAndInitWithAccounts(accounts.toDb());
|
||||||
final accounts = _c.pref.getAccounts3Or([]);
|
|
||||||
for (final a in accounts) {
|
|
||||||
await db.insertAccountOf(a);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
emit(state.copyWith(lastSuccessful: ev));
|
emit(state.copyWith(lastSuccessful: ev));
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[_onClearCacheDatabase] Uncaught exception", e, stackTrace);
|
_log.shout("[_onClearCacheDatabase] Uncaught exception", e, stackTrace);
|
||||||
|
@ -39,5 +36,6 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
|
||||||
}
|
}
|
||||||
|
|
||||||
final DiContainer _c;
|
final DiContainer _c;
|
||||||
|
final NpDb db;
|
||||||
final _errorStream = StreamController<_Error>.broadcast();
|
final _errorStream = StreamController<_Error>.broadcast();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,14 @@ import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc_util.dart';
|
import 'package:nc_photos/bloc_util.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart';
|
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
part 'expert/bloc.dart';
|
part 'expert/bloc.dart';
|
||||||
|
@ -26,7 +27,10 @@ class ExpertSettings extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) => _Bloc(KiwiContainer().resolve<DiContainer>()),
|
create: (_) => _Bloc(
|
||||||
|
KiwiContainer().resolve<DiContainer>(),
|
||||||
|
db: context.read(),
|
||||||
|
),
|
||||||
child: const _WrappedExpertSettings(),
|
child: const _WrappedExpertSettings(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,13 @@ import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/pref_util.dart' as pref_util;
|
import 'package:nc_photos/entity/pref_util.dart' as pref_util;
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
|
import 'package:nc_photos/legacy/sign_in.dart' as legacy;
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/connect.dart';
|
import 'package:nc_photos/widget/connect.dart';
|
||||||
|
@ -17,6 +16,7 @@ import 'package:nc_photos/widget/home.dart';
|
||||||
import 'package:nc_photos/widget/root_picker.dart';
|
import 'package:nc_photos/widget/root_picker.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
|
|
||||||
part 'sign_in.g.dart';
|
part 'sign_in.g.dart';
|
||||||
|
@ -174,10 +174,7 @@ class _SignInState extends State<SignIn> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _persistAccount(Account account) async {
|
Future<void> _persistAccount(Account account) async {
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
await context.read<NpDb>().addAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.use((db) async {
|
|
||||||
await db.insertAccountOf(account);
|
|
||||||
});
|
|
||||||
// only signing in with app password would trigger distinct
|
// only signing in with app password would trigger distinct
|
||||||
final accounts = (Pref().getAccounts3Or([])..add(account)).distinct();
|
final accounts = (Pref().getAccounts3Or([])..add(account)).distinct();
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -6,9 +6,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/mobile/android/activity.dart';
|
import 'package:nc_photos/mobile/android/activity.dart';
|
||||||
import 'package:nc_photos/mobile/android/permission_util.dart';
|
import 'package:nc_photos/mobile/android/permission_util.dart';
|
||||||
|
@ -20,6 +20,7 @@ import 'package:nc_photos/widget/home.dart';
|
||||||
import 'package:nc_photos/widget/setup.dart';
|
import 'package:nc_photos/widget/setup.dart';
|
||||||
import 'package:nc_photos/widget/sign_in.dart';
|
import 'package:nc_photos/widget/sign_in.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_platform_permission/np_platform_permission.dart';
|
import 'package:np_platform_permission/np_platform_permission.dart';
|
||||||
import 'package:np_platform_util/np_platform_util.dart';
|
import 'package:np_platform_util/np_platform_util.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
@ -206,7 +207,7 @@ class _SplashState extends State<Splash> {
|
||||||
try {
|
try {
|
||||||
_log.info("[_upgrade46] insertDbAccounts");
|
_log.info("[_upgrade46] insertDbAccounts");
|
||||||
final c = KiwiContainer().resolve<DiContainer>();
|
final c = KiwiContainer().resolve<DiContainer>();
|
||||||
await CompatV46.insertDbAccounts(Pref(), c.sqliteDb);
|
await CompatV46.insertDbAccounts(c.pref, context.read());
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[_upgrade46] Failed while clearDefaultCache", e, stackTrace);
|
_log.shout("[_upgrade46] Failed while clearDefaultCache", e, stackTrace);
|
||||||
unawaited(Pref().setAccounts3(null));
|
unawaited(Pref().setAccounts3(null));
|
||||||
|
@ -235,7 +236,7 @@ class _SplashState extends State<Splash> {
|
||||||
try {
|
try {
|
||||||
_log.info("[_upgrade55] migrate DB");
|
_log.info("[_upgrade55] migrate DB");
|
||||||
await CompatV55.migrateDb(
|
await CompatV55.migrateDb(
|
||||||
c.sqliteDb,
|
c.npDb,
|
||||||
onProgress: (current, count) {
|
onProgress: (current, count) {
|
||||||
_upgradeCubit.setState(
|
_upgradeCubit.setState(
|
||||||
L10n.global().migrateDatabaseProcessingNotification,
|
L10n.global().migrateDatabaseProcessingNotification,
|
||||||
|
@ -246,13 +247,8 @@ class _SplashState extends State<Splash> {
|
||||||
);
|
);
|
||||||
} catch (e, stackTrace) {
|
} catch (e, stackTrace) {
|
||||||
_log.shout("[_upgrade55] Failed while migrateDb", e, stackTrace);
|
_log.shout("[_upgrade55] Failed while migrateDb", e, stackTrace);
|
||||||
await c.sqliteDb.use((db) async {
|
final accounts = Pref().getAccounts3Or([]);
|
||||||
await db.truncate();
|
await context.read<NpDb>().clearAndInitWithAccounts(accounts.toDb());
|
||||||
final accounts = Pref().getAccounts3Or([]);
|
|
||||||
for (final a in accounts) {
|
|
||||||
await db.insertAccountOf(a);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
_upgradeCubit.setIntermediate();
|
_upgradeCubit.setIntermediate();
|
||||||
}
|
}
|
||||||
|
|
|
@ -202,14 +202,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.1"
|
||||||
charcode:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: charcode
|
|
||||||
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.1"
|
|
||||||
checked_yaml:
|
checked_yaml:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -226,14 +218,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
cli_util:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: cli_util
|
|
||||||
sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.3.5"
|
|
||||||
clock:
|
clock:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -398,21 +382,13 @@ packages:
|
||||||
source: git
|
source: git
|
||||||
version: "0.1.0"
|
version: "0.1.0"
|
||||||
drift:
|
drift:
|
||||||
dependency: "direct main"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: drift
|
name: drift
|
||||||
sha256: "21abd7b1c1a637a264f58f9f05c7b910d29c204aab1cbfcb4d9fada1e98a9303"
|
sha256: "21abd7b1c1a637a264f58f9f05c7b910d29c204aab1cbfcb4d9fada1e98a9303"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.0"
|
version: "2.8.0"
|
||||||
drift_dev:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: drift_dev
|
|
||||||
sha256: ac1454f20b4b721bfbde7bd3b05123150bae2196ef992c405c07a63f5976a397
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "2.8.0"
|
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -983,6 +959,27 @@ packages:
|
||||||
relative: true
|
relative: true
|
||||||
source: path
|
source: path
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
np_datetime:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../np_datetime"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
|
np_db:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
path: "../np_db"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
|
np_db_sqlite:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
path: "../np_db_sqlite"
|
||||||
|
relative: true
|
||||||
|
source: path
|
||||||
|
version: "1.0.0"
|
||||||
np_geocoder:
|
np_geocoder:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1296,14 +1293,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.1"
|
||||||
recase:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: recase
|
|
||||||
sha256: e4eb4ec2dcdee52dcf99cb4ceabaffc631d7424ee55e56f280bc039737f89213
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "4.1.0"
|
|
||||||
rxdart:
|
rxdart:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -1518,7 +1507,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5"
|
version: "2.4.5"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3
|
name: sqlite3
|
||||||
sha256: "2cef47b59d310e56f8275b13734ee80a9cf4a48a43172020cb55a620121fbf66"
|
sha256: "2cef47b59d310e56f8275b13734ee80a9cf4a48a43172020cb55a620121fbf66"
|
||||||
|
@ -1526,21 +1515,13 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.1"
|
version: "1.11.1"
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: sqlite3_flutter_libs
|
name: sqlite3_flutter_libs
|
||||||
sha256: "1e20a88d5c7ae8400e009f38ddbe8b001800a6dffa37832481a86a219bc904c7"
|
sha256: "1e20a88d5c7ae8400e009f38ddbe8b001800a6dffa37832481a86a219bc904c7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.15"
|
version: "0.5.15"
|
||||||
sqlparser:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: sqlparser
|
|
||||||
sha256: b5b24c2804d39cbd619b424d8c9b1321cc5e813fd0e7b95a2707f596f82d5cd3
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.29.0"
|
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1907,4 +1888,4 @@ packages:
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.19.6 <3.0.0"
|
dart: ">=2.19.6 <3.0.0"
|
||||||
flutter: ">=3.4.0-17.0.pre"
|
flutter: ">=3.7.0"
|
||||||
|
|
|
@ -52,7 +52,6 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar
|
url: https://gitlab.com/nc-photos/flutter-draggable-scrollbar
|
||||||
ref: v0.1.0-nc-photos-6
|
ref: v0.1.0-nc-photos-6
|
||||||
drift: 2.8.0
|
|
||||||
dynamic_color: ^1.6.6
|
dynamic_color: ^1.6.6
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
event_bus: ^2.0.0
|
event_bus: ^2.0.0
|
||||||
|
@ -101,6 +100,10 @@ dependencies:
|
||||||
path: ../np_common
|
path: ../np_common
|
||||||
np_collection:
|
np_collection:
|
||||||
path: ../np_collection
|
path: ../np_collection
|
||||||
|
np_datetime:
|
||||||
|
path: ../np_datetime
|
||||||
|
np_db:
|
||||||
|
path: ../np_db
|
||||||
np_geocoder:
|
np_geocoder:
|
||||||
path: ../np_geocoder
|
path: ../np_geocoder
|
||||||
np_gps_map:
|
np_gps_map:
|
||||||
|
@ -139,8 +142,6 @@ dependencies:
|
||||||
shared_preferences_platform_interface: any
|
shared_preferences_platform_interface: any
|
||||||
sliver_tools: ^0.2.10
|
sliver_tools: ^0.2.10
|
||||||
smooth_corner: ^1.1.0
|
smooth_corner: ^1.1.0
|
||||||
sqlite3: any
|
|
||||||
sqlite3_flutter_libs: ^0.5.15
|
|
||||||
to_string:
|
to_string:
|
||||||
git:
|
git:
|
||||||
url: https://gitlab.com/nkming2/dart-to-string
|
url: https://gitlab.com/nkming2/dart-to-string
|
||||||
|
@ -176,13 +177,15 @@ dev_dependencies:
|
||||||
path: copy_with_build
|
path: copy_with_build
|
||||||
ref: copy_with_build-1.7.0
|
ref: copy_with_build-1.7.0
|
||||||
dart_code_metrics: any
|
dart_code_metrics: any
|
||||||
drift_dev: 2.8.0
|
drift: 2.8.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
# integration_test:
|
# integration_test:
|
||||||
# sdk: flutter
|
# sdk: flutter
|
||||||
np_codegen_build:
|
np_codegen_build:
|
||||||
path: ../codegen_build
|
path: ../codegen_build
|
||||||
|
np_db_sqlite:
|
||||||
|
path: ../np_db_sqlite
|
||||||
np_lints:
|
np_lints:
|
||||||
path: ../np_lints
|
path: ../np_lints
|
||||||
to_string_build:
|
to_string_build:
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
import 'package:nc_photos/bloc/list_album_share_outlier.dart';
|
import 'package:nc_photos/bloc/list_album_share_outlier.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ void _initialState() {
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
shareeRepo: MockShareeRepo(),
|
shareeRepo: MockShareeRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
final bloc = ListAlbumShareOutlierBloc(c);
|
final bloc = ListAlbumShareOutlierBloc(c);
|
||||||
|
@ -85,10 +86,10 @@ void _testQueryUnsharedAlbumExtraShare(String description) {
|
||||||
shareeRepo: MockShareeMemoryRepo([
|
shareeRepo: MockShareeMemoryRepo([
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -128,7 +129,7 @@ void _testQueryUnsharedAlbumExtraJsonShare(String description) {
|
||||||
shareeRepo: MockShareeMemoryRepo([
|
shareeRepo: MockShareeMemoryRepo([
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tearDown: () => c.sqliteDb.close(),
|
tearDown: () => c.sqliteDb.close(),
|
||||||
|
@ -172,10 +173,10 @@ void _testQuerySharedAlbumMissingShare(String description) {
|
||||||
shareeRepo: MockShareeMemoryRepo([
|
shareeRepo: MockShareeMemoryRepo([
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -226,10 +227,10 @@ void _testQuerySharedAlbumMissingManagedShareOtherAdded(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -286,11 +287,11 @@ void _testQuerySharedAlbumMissingManagedShareOtherReshared(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
});
|
});
|
||||||
|
@ -339,10 +340,10 @@ void _testQuerySharedAlbumMissingUnmanagedShareOtherAdded(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -374,7 +375,7 @@ void _testQuerySharedAlbumMissingJsonShare(String description) {
|
||||||
shareeRepo: MockShareeMemoryRepo([
|
shareeRepo: MockShareeMemoryRepo([
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tearDown: () => c.sqliteDb.close(),
|
tearDown: () => c.sqliteDb.close(),
|
||||||
|
@ -421,10 +422,10 @@ void _testQuerySharedAlbumExtraShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -479,11 +480,11 @@ void _testQuerySharedAlbumExtraShareOtherAdded(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
});
|
});
|
||||||
|
@ -545,11 +546,11 @@ void _testQuerySharedAlbumExtraUnmanagedShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
});
|
});
|
||||||
|
@ -587,7 +588,7 @@ void _testQuerySharedAlbumExtraJsonShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tearDown: () => c.sqliteDb.close(),
|
tearDown: () => c.sqliteDb.close(),
|
||||||
|
@ -632,10 +633,10 @@ void _testQuerySharedAlbumNotOwnedMissingShareToOwner(String description) {
|
||||||
shareeRepo: MockShareeMemoryRepo([
|
shareeRepo: MockShareeMemoryRepo([
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -686,10 +687,10 @@ void _testQuerySharedAlbumNotOwnedMissingManagedShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -738,10 +739,10 @@ void _testQuerySharedAlbumNotOwnedMissingUnmanagedShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -781,7 +782,7 @@ void _testQuerySharedAlbumNotOwnedMissingJsonShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tearDown: () => c.sqliteDb.close(),
|
tearDown: () => c.sqliteDb.close(),
|
||||||
|
@ -825,10 +826,10 @@ void _testQuerySharedAlbumNotOwnedExtraManagedShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -878,10 +879,10 @@ void _testQuerySharedAlbumNotOwnedExtraUnmanagedShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -926,7 +927,7 @@ void _testQuerySharedAlbumNotOwnedExtraJsonShare(String description) {
|
||||||
util.buildSharee(shareWith: "user1".toCi()),
|
util.buildSharee(shareWith: "user1".toCi()),
|
||||||
util.buildSharee(shareWith: "user2".toCi()),
|
util.buildSharee(shareWith: "user2".toCi()),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
tearDown: () => c.sqliteDb.close(),
|
tearDown: () => c.sqliteDb.close(),
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
|
@ -6,9 +7,9 @@ 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/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/exception.dart';
|
import 'package:nc_photos/exception.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -43,11 +44,11 @@ Future<void> _dbGet() async {
|
||||||
(util.AlbumBuilder.ofId(albumId: 1)).build(),
|
(util.AlbumBuilder.ofId(albumId: 1)).build(),
|
||||||
];
|
];
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(
|
await util.insertFiles(
|
||||||
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
||||||
await util.insertAlbums(c.sqliteDb, account, albums);
|
await util.insertAlbums(c.sqliteDb, account, albums);
|
||||||
|
@ -66,11 +67,11 @@ Future<void> _dbGetNa() async {
|
||||||
(util.AlbumBuilder.ofId(albumId: 0)).build(),
|
(util.AlbumBuilder.ofId(albumId: 0)).build(),
|
||||||
];
|
];
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
final src = AlbumSqliteDbDataSource(c);
|
final src = AlbumSqliteDbDataSource(c);
|
||||||
|
@ -91,11 +92,11 @@ Future<void> _dbGetAll() async {
|
||||||
(util.AlbumBuilder.ofId(albumId: 2)).build(),
|
(util.AlbumBuilder.ofId(albumId: 2)).build(),
|
||||||
];
|
];
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(
|
await util.insertFiles(
|
||||||
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
||||||
await util.insertAlbums(c.sqliteDb, account, albums);
|
await util.insertAlbums(c.sqliteDb, account, albums);
|
||||||
|
@ -120,11 +121,11 @@ Future<void> _dbGetAllNa() async {
|
||||||
(util.AlbumBuilder.ofId(albumId: 2)).build(),
|
(util.AlbumBuilder.ofId(albumId: 2)).build(),
|
||||||
];
|
];
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, [albums[0].albumFile!]);
|
await util.insertFiles(c.sqliteDb, account, [albums[0].albumFile!]);
|
||||||
await util.insertAlbums(c.sqliteDb, account, [albums[0]]);
|
await util.insertAlbums(c.sqliteDb, account, [albums[0]]);
|
||||||
});
|
});
|
||||||
|
@ -154,11 +155,11 @@ Future<void> _dbUpdateExisting() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(
|
await util.insertFiles(
|
||||||
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
||||||
await util.insertAlbums(c.sqliteDb, account, albums);
|
await util.insertAlbums(c.sqliteDb, account, albums);
|
||||||
|
@ -202,11 +203,11 @@ Future<void> _dbUpdateNew() async {
|
||||||
];
|
];
|
||||||
final newAlbum = (util.AlbumBuilder.ofId(albumId: 1)).build();
|
final newAlbum = (util.AlbumBuilder.ofId(albumId: 1)).build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account,
|
await util.insertFiles(c.sqliteDb, account,
|
||||||
[...albums.map((a) => a.albumFile!), newAlbum.albumFile!]);
|
[...albums.map((a) => a.albumFile!), newAlbum.albumFile!]);
|
||||||
await util.insertAlbums(c.sqliteDb, account, albums);
|
await util.insertAlbums(c.sqliteDb, account, albums);
|
||||||
|
@ -234,11 +235,11 @@ Future<void> _dbUpdateShares() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(
|
await util.insertFiles(
|
||||||
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
||||||
await util.insertAlbums(c.sqliteDb, account, albums);
|
await util.insertAlbums(c.sqliteDb, account, albums);
|
||||||
|
@ -296,11 +297,11 @@ Future<void> _dbUpdateDeleteShares() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(
|
await util.insertFiles(
|
||||||
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
c.sqliteDb, account, albums.map((a) => a.albumFile!));
|
||||||
await util.insertAlbums(c.sqliteDb, account, albums);
|
await util.insertAlbums(c.sqliteDb, account, albums);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
|
@ -10,8 +8,8 @@ import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/upgrader.dart';
|
import 'package:nc_photos/entity/album/upgrader.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_common/type.dart';
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -1956,10 +1954,6 @@ void _toAppDbJsonShares() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
String _stripJsonString(String str) {
|
|
||||||
return jsonEncode(jsonDecode(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NullAlbumUpgraderFactory extends AlbumUpgraderFactory {
|
class _NullAlbumUpgraderFactory extends AlbumUpgraderFactory {
|
||||||
const _NullAlbumUpgraderFactory();
|
const _NullAlbumUpgraderFactory();
|
||||||
|
|
||||||
|
|
|
@ -345,17 +345,16 @@ void _upgradeV8JsonAutoNoFileId() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _upgradeV8DbNonManualCover() {
|
void _upgradeV8DbNonManualCover() {
|
||||||
final dbObj = sql.Album(
|
final dbObj = DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "memory",
|
coverProviderType: "memory",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fdId": 1,
|
"fdId": 1,
|
||||||
|
@ -364,23 +363,23 @@ void _upgradeV8DbNonManualCover() {
|
||||||
"fdIsFavorite": false,
|
"fdIsFavorite": false,
|
||||||
"fdDateTime": "2020-01-02T03:04:05.678901Z"
|
"fdDateTime": "2020-01-02T03:04:05.678901Z"
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
const AlbumUpgraderV8().doDb(dbObj),
|
const AlbumUpgraderV8().doDb(dbObj),
|
||||||
sql.Album(
|
DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "memory",
|
coverProviderType: "memory",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fdId": 1,
|
"fdId": 1,
|
||||||
|
@ -389,47 +388,47 @@ void _upgradeV8DbNonManualCover() {
|
||||||
"fdIsFavorite": false,
|
"fdIsFavorite": false,
|
||||||
"fdDateTime": "2020-01-02T03:04:05.678901Z"
|
"fdDateTime": "2020-01-02T03:04:05.678901Z"
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _upgradeV8DbManualNow() {
|
void _upgradeV8DbManualNow() {
|
||||||
withClock(Clock.fixed(DateTime.utc(2020, 1, 2, 3, 4, 5)), () {
|
withClock(Clock.fixed(DateTime.utc(2020, 1, 2, 3, 4, 5)), () {
|
||||||
final dbObj = sql.Album(
|
final dbObj = DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "manual",
|
coverProviderType: "manual",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fileId": 1
|
"fileId": 1
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
const AlbumUpgraderV8().doDb(dbObj),
|
const AlbumUpgraderV8().doDb(dbObj),
|
||||||
sql.Album(
|
DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "manual",
|
coverProviderType: "manual",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fdId": 1,
|
"fdId": 1,
|
||||||
|
@ -438,137 +437,135 @@ void _upgradeV8DbManualNow() {
|
||||||
"fdIsFavorite": false,
|
"fdIsFavorite": false,
|
||||||
"fdDateTime": "2020-01-02T03:04:05.000Z"
|
"fdDateTime": "2020-01-02T03:04:05.000Z"
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _upgradeV8DbManualExifTime() {
|
void _upgradeV8DbManualExifTime() {
|
||||||
final dbObj = sql.Album(
|
final dbObj = DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "manual",
|
coverProviderType: "manual",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fileId": 1,
|
"fileId": 1,
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"exif": {
|
"exif": {"DateTimeOriginal": "2020:01:02 03:04:05"}
|
||||||
"DateTimeOriginal": "2020:01:02 03:04:05"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
);
|
);
|
||||||
// dart does not provide a way to mock timezone
|
// dart does not provide a way to mock timezone
|
||||||
final dateTime = DateTime(2020, 1, 2, 3, 4, 5).toUtc().toIso8601String();
|
final dateTime = DateTime(2020, 1, 2, 3, 4, 5).toUtc().toIso8601String();
|
||||||
expect(
|
expect(
|
||||||
const AlbumUpgraderV8().doDb(dbObj),
|
const AlbumUpgraderV8().doDb(dbObj),
|
||||||
sql.Album(
|
DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "manual",
|
coverProviderType: "manual",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fdId": 1,
|
"fdId": 1,
|
||||||
"fdMime": null,
|
"fdMime": null,
|
||||||
"fdIsArchived": false,
|
"fdIsArchived": false,
|
||||||
"fdIsFavorite": false,
|
"fdIsFavorite": false,
|
||||||
"fdDateTime": "$dateTime"
|
"fdDateTime": dateTime,
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _upgradeV8DbAutoNull() {
|
void _upgradeV8DbAutoNull() {
|
||||||
final dbObj = sql.Album(
|
final dbObj = DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "auto",
|
coverProviderType: "auto",
|
||||||
coverProviderContent: "{}",
|
coverProviderContent: {},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
const AlbumUpgraderV8().doDb(dbObj),
|
const AlbumUpgraderV8().doDb(dbObj),
|
||||||
sql.Album(
|
DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "auto",
|
coverProviderType: "auto",
|
||||||
coverProviderContent: "{}",
|
coverProviderContent: {},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _upgradeV8DbAutoLastModified() {
|
void _upgradeV8DbAutoLastModified() {
|
||||||
final dbObj = sql.Album(
|
final dbObj = DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "auto",
|
coverProviderType: "auto",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fileId": 1,
|
"fileId": 1,
|
||||||
"lastModified": "2020-01-02T03:04:05.000Z"
|
"lastModified": "2020-01-02T03:04:05.000Z"
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
const AlbumUpgraderV8().doDb(dbObj),
|
const AlbumUpgraderV8().doDb(dbObj),
|
||||||
sql.Album(
|
DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "auto",
|
coverProviderType: "auto",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
"fdPath": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"fdId": 1,
|
"fdId": 1,
|
||||||
|
@ -577,48 +574,49 @@ void _upgradeV8DbAutoLastModified() {
|
||||||
"fdIsFavorite": false,
|
"fdIsFavorite": false,
|
||||||
"fdDateTime": "2020-01-02T03:04:05.000Z"
|
"fdDateTime": "2020-01-02T03:04:05.000Z"
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _upgradeV8DbAutoNoFileId() {
|
void _upgradeV8DbAutoNoFileId() {
|
||||||
final dbObj = sql.Album(
|
final dbObj = DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "auto",
|
coverProviderType: "auto",
|
||||||
coverProviderContent: _stripJsonString("""{
|
coverProviderContent: {
|
||||||
"coverFile": {
|
"coverFile": {
|
||||||
"path": "remote.php/dav/files/admin/test1.jpg",
|
"path": "remote.php/dav/files/admin/test1.jpg",
|
||||||
"lastModified": "2020-01-02T03:04:05.000Z"
|
"lastModified": "2020-01-02T03:04:05.000Z"
|
||||||
}
|
}
|
||||||
}"""),
|
},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
const AlbumUpgraderV8().doDb(dbObj),
|
const AlbumUpgraderV8().doDb(dbObj),
|
||||||
sql.Album(
|
DbAlbum(
|
||||||
rowId: 1,
|
fileId: 1,
|
||||||
file: 1,
|
|
||||||
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
fileEtag: "8a3e0799b6f0711c23cc2d93950eceb5",
|
||||||
version: 8,
|
version: 8,
|
||||||
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
lastUpdated: DateTime.utc(2020, 1, 2, 3, 4, 5),
|
||||||
name: "test1",
|
name: "test1",
|
||||||
providerType: "static",
|
providerType: "static",
|
||||||
providerContent: """{"items": []}""",
|
providerContent: {"items": []},
|
||||||
coverProviderType: "auto",
|
coverProviderType: "auto",
|
||||||
coverProviderContent: "{}",
|
coverProviderContent: {},
|
||||||
sortProviderType: "null",
|
sortProviderType: "null",
|
||||||
sortProviderContent: "{}",
|
sortProviderContent: {},
|
||||||
|
shares: [],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.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/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../../test_util.dart' as util;
|
import '../../test_util.dart' as util;
|
||||||
|
@ -43,11 +44,11 @@ Future<void> _list() async {
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -66,11 +67,11 @@ Future<void> _listSingle() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final files = (util.FilesBuilder()..addDir("admin")).build();
|
final files = (util.FilesBuilder()..addDir("admin")).build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], const []);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], const []);
|
||||||
});
|
});
|
||||||
|
@ -90,11 +91,11 @@ Future<void> _removeFile() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
});
|
});
|
||||||
|
@ -117,11 +118,11 @@ Future<void> _removeEmptyDir() async {
|
||||||
..addDir("admin/test"))
|
..addDir("admin/test"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[1], const []);
|
await util.insertDirRelation(c.sqliteDb, account, files[1], const []);
|
||||||
|
@ -150,11 +151,11 @@ Future<void> _removeDir() async {
|
||||||
..addJpeg("admin/test/test1.jpg"))
|
..addJpeg("admin/test/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]);
|
||||||
|
@ -180,11 +181,11 @@ Future<void> _removeDirWithSubDir() async {
|
||||||
..addJpeg("admin/test/test2/test3.jpg"))
|
..addJpeg("admin/test/test2/test3.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[1], [files[2]]);
|
||||||
|
@ -215,11 +216,11 @@ Future<void> _updateFileProperty() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
});
|
});
|
||||||
|
@ -257,11 +258,11 @@ Future<void> _updateMetadata() async {
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
});
|
});
|
||||||
|
@ -299,11 +300,11 @@ Future<void> _updateAddMetadata() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
});
|
});
|
||||||
|
@ -345,11 +346,11 @@ Future<void> _updateDeleteMetadata() async {
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.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/data_source.dart';
|
import 'package:nc_photos/entity/file/data_source.dart';
|
||||||
import 'package:nc_photos/entity/file/file_cache_manager.dart';
|
import 'package:nc_photos/entity/file/file_cache_manager.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:np_math/np_math.dart';
|
import 'package:np_math/np_math.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -53,11 +54,11 @@ Future<void> _loaderNoCache() async {
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
fileRepo: MockFileMemoryRepo(files),
|
fileRepo: MockFileMemoryRepo(files),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
final cacheSrc = FileSqliteDbDataSource(c);
|
final cacheSrc = FileSqliteDbDataSource(c);
|
||||||
|
@ -80,7 +81,7 @@ Future<void> _loaderOutdatedCache() async {
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
fileRepo: MockFileMemoryRepo(files),
|
fileRepo: MockFileMemoryRepo(files),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
final dbFiles = [
|
final dbFiles = [
|
||||||
|
@ -88,7 +89,7 @@ Future<void> _loaderOutdatedCache() async {
|
||||||
...files.slice(1),
|
...files.slice(1),
|
||||||
];
|
];
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, dbFiles);
|
await util.insertFiles(c.sqliteDb, account, dbFiles);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, dbFiles[0], dbFiles.slice(1, 3));
|
c.sqliteDb, account, dbFiles[0], dbFiles.slice(1, 3));
|
||||||
|
@ -119,11 +120,11 @@ Future<void> _loaderQueryRemoteSameEtag() async {
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
fileRepo: MockFileMemoryRepo(files),
|
fileRepo: MockFileMemoryRepo(files),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -155,7 +156,7 @@ Future<void> _loaderQueryRemoteDiffEtag() async {
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
fileRepo: MockFileMemoryRepo(files),
|
fileRepo: MockFileMemoryRepo(files),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
final dbFiles = [
|
final dbFiles = [
|
||||||
|
@ -163,7 +164,7 @@ Future<void> _loaderQueryRemoteDiffEtag() async {
|
||||||
...files.slice(1),
|
...files.slice(1),
|
||||||
];
|
];
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, dbFiles);
|
await util.insertFiles(c.sqliteDb, account, dbFiles);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, dbFiles[0], dbFiles.slice(1, 3));
|
c.sqliteDb, account, dbFiles[0], dbFiles.slice(1, 3));
|
||||||
|
@ -193,11 +194,11 @@ Future<void> _updaterIdentical() async {
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -228,11 +229,11 @@ Future<void> _updaterNewFile() async {
|
||||||
.build()
|
.build()
|
||||||
.first;
|
.first;
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -259,11 +260,11 @@ Future<void> _updaterDeleteFile() async {
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -293,11 +294,11 @@ Future<void> _updaterDeleteDir() async {
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -331,11 +332,11 @@ Future<void> _updaterUpdateFile() async {
|
||||||
.build();
|
.build();
|
||||||
final newFile = files[1].copyWith(contentLength: 654);
|
final newFile = files[1].copyWith(contentLength: 654);
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -369,12 +370,12 @@ Future<void> _updaterNewSharedFile() async {
|
||||||
user1Files
|
user1Files
|
||||||
.add(files[1].copyWith(path: "remote.php/dav/files/user1/test1.jpg"));
|
.add(files[1].copyWith(path: "remote.php/dav/files/user1/test1.jpg"));
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -406,12 +407,12 @@ Future<void> _updaterNewSharedDir() async {
|
||||||
user1Files.add(
|
user1Files.add(
|
||||||
files[3].copyWith(path: "remote.php/dav/files/user1/share/test2.jpg"));
|
files[3].copyWith(path: "remote.php/dav/files/user1/share/test2.jpg"));
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -444,12 +445,12 @@ Future<void> _updaterDeleteSharedFile() async {
|
||||||
user1Files
|
user1Files
|
||||||
.add(files[1].copyWith(path: "remote.php/dav/files/user1/test1.jpg"));
|
.add(files[1].copyWith(path: "remote.php/dav/files/user1/test1.jpg"));
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -487,12 +488,12 @@ Future<void> _updaterDeleteSharedDir() async {
|
||||||
user1Files.add(
|
user1Files.add(
|
||||||
files[3].copyWith(path: "remote.php/dav/files/user1/share/test2.jpg"));
|
files[3].copyWith(path: "remote.php/dav/files/user1/share/test2.jpg"));
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -529,11 +530,11 @@ Future<void> _updaterTooManyFiles() async {
|
||||||
}
|
}
|
||||||
final newFiles = newFilesBuilder.build();
|
final newFiles = newFilesBuilder.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -558,11 +559,11 @@ Future<void> _updaterMovedFileToFront() async {
|
||||||
..addJpeg("admin/test2/test1.jpg"))
|
..addJpeg("admin/test2/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -605,11 +606,11 @@ Future<void> _updaterMovedFileToBehind() async {
|
||||||
..addJpeg("admin/test1/test1.jpg"))
|
..addJpeg("admin/test1/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(
|
await util.insertDirRelation(
|
||||||
c.sqliteDb, account, files[0], files.slice(1, 3));
|
c.sqliteDb, account, files[0], files.slice(1, 3));
|
||||||
|
@ -654,11 +655,11 @@ Future<void> _emptier() async {
|
||||||
..addJpeg("admin/testB/test2.jpg"))
|
..addJpeg("admin/testB/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util
|
await util
|
||||||
.insertDirRelation(c.sqliteDb, account, files[0], [files[1], files[3]]);
|
.insertDirRelation(c.sqliteDb, account, files[0], [files[1], files[3]]);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.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/sqlite/database.dart' as sql;
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:nc_photos/object_extension.dart';
|
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../../test_util.dart' as util;
|
import '../../test_util.dart' as util;
|
||||||
|
@ -18,7 +18,6 @@ void main() {
|
||||||
test("same server", _deleteAccountSameServer);
|
test("same server", _deleteAccountSameServer);
|
||||||
test("same server shared file", _deleteAccountSameServerSharedFile);
|
test("same server shared file", _deleteAccountSameServerSharedFile);
|
||||||
});
|
});
|
||||||
test("cleanUpDanglingFiles", _cleanUpDanglingFiles);
|
|
||||||
test("truncate", _truncate);
|
test("truncate", _truncate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -29,19 +28,19 @@ void main() {
|
||||||
Future<void> _insertAccountFirst() async {
|
Future<void> _insertAccountFirst() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
|
||||||
await c.sqliteDb.use((db) async {
|
await c.sqliteDb.use((db) async {
|
||||||
await db.insertAccountOf(account);
|
await db.insertAccounts([account.toDb()]);
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
||||||
{
|
{
|
||||||
const util.SqlAccountWithServer(
|
const util.SqlAccountWithServer(
|
||||||
sql.Server(rowId: 1, address: "http://example.com"),
|
compat.Server(rowId: 1, address: "http://example.com"),
|
||||||
sql.Account(rowId: 1, server: 1, userId: "admin"),
|
compat.Account(rowId: 1, server: 1, userId: "admin"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -54,26 +53,26 @@ Future<void> _insertAccountSameServer() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final user1Account = util.buildAccount(userId: "user1");
|
final user1Account = util.buildAccount(userId: "user1");
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
await c.sqliteDb.use((db) async {
|
await c.sqliteDb.use((db) async {
|
||||||
await db.insertAccountOf(user1Account);
|
await db.insertAccounts([user1Account.toDb()]);
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
||||||
{
|
{
|
||||||
const util.SqlAccountWithServer(
|
const util.SqlAccountWithServer(
|
||||||
sql.Server(rowId: 1, address: "http://example.com"),
|
compat.Server(rowId: 1, address: "http://example.com"),
|
||||||
sql.Account(rowId: 1, server: 1, userId: "admin"),
|
compat.Account(rowId: 1, server: 1, userId: "admin"),
|
||||||
),
|
),
|
||||||
const util.SqlAccountWithServer(
|
const util.SqlAccountWithServer(
|
||||||
sql.Server(rowId: 1, address: "http://example.com"),
|
compat.Server(rowId: 1, address: "http://example.com"),
|
||||||
sql.Account(rowId: 2, server: 1, userId: "user1"),
|
compat.Account(rowId: 2, server: 1, userId: "user1"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -86,22 +85,22 @@ Future<void> _insertAccountSameAccount() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final account2 = util.buildAccount();
|
final account2 = util.buildAccount();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
await c.sqliteDb.use((db) async {
|
await c.sqliteDb.use((db) async {
|
||||||
await db.insertAccountOf(account2);
|
await db.insertAccounts([account2.toDb()]);
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
||||||
{
|
{
|
||||||
const util.SqlAccountWithServer(
|
const util.SqlAccountWithServer(
|
||||||
sql.Server(rowId: 1, address: "http://example.com"),
|
compat.Server(rowId: 1, address: "http://example.com"),
|
||||||
sql.Account(rowId: 1, server: 1, userId: "admin"),
|
compat.Account(rowId: 1, server: 1, userId: "admin"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -119,16 +118,16 @@ Future<void> _deleteAccount() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
await c.sqliteDb.use((db) async {
|
await c.sqliteDb.use((db) async {
|
||||||
await db.deleteAccountOf(account);
|
await db.deleteAccount(account.toDb());
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
||||||
|
@ -157,12 +156,12 @@ Future<void> _deleteAccountSameServer() async {
|
||||||
..addJpeg("user1/test2.jpg", ownerId: "user1"))
|
..addJpeg("user1/test2.jpg", ownerId: "user1"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
|
|
||||||
|
@ -172,14 +171,14 @@ Future<void> _deleteAccountSameServer() async {
|
||||||
});
|
});
|
||||||
|
|
||||||
await c.sqliteDb.use((db) async {
|
await c.sqliteDb.use((db) async {
|
||||||
await db.deleteAccountOf(account);
|
await db.deleteAccount(account.toDb());
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
||||||
{
|
{
|
||||||
const util.SqlAccountWithServer(
|
const util.SqlAccountWithServer(
|
||||||
sql.Server(rowId: 1, address: "http://example.com"),
|
compat.Server(rowId: 1, address: "http://example.com"),
|
||||||
sql.Account(rowId: 2, server: 1, userId: "user1"),
|
compat.Account(rowId: 2, server: 1, userId: "user1"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -208,12 +207,12 @@ Future<void> _deleteAccountSameServerSharedFile() async {
|
||||||
user1Files
|
user1Files
|
||||||
.add(files[0].copyWith(path: "remote.php/dav/files/user1/test1.jpg"));
|
.add(files[0].copyWith(path: "remote.php/dav/files/user1/test1.jpg"));
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
await util.insertDirRelation(c.sqliteDb, account, files[0], [files[1]]);
|
||||||
|
|
||||||
|
@ -223,14 +222,14 @@ Future<void> _deleteAccountSameServerSharedFile() async {
|
||||||
});
|
});
|
||||||
|
|
||||||
await c.sqliteDb.use((db) async {
|
await c.sqliteDb.use((db) async {
|
||||||
await db.deleteAccountOf(account);
|
await db.deleteAccount(account.toDb());
|
||||||
});
|
});
|
||||||
expect(
|
expect(
|
||||||
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
await util.listSqliteDbServerAccounts(c.sqliteDb),
|
||||||
{
|
{
|
||||||
const util.SqlAccountWithServer(
|
const util.SqlAccountWithServer(
|
||||||
sql.Server(rowId: 1, address: "http://example.com"),
|
compat.Server(rowId: 1, address: "http://example.com"),
|
||||||
sql.Account(rowId: 2, server: 1, userId: "user1"),
|
compat.Account(rowId: 2, server: 1, userId: "user1"),
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -240,44 +239,6 @@ Future<void> _deleteAccountSameServerSharedFile() async {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clean up Files without an associated entry in AccountFiles
|
|
||||||
///
|
|
||||||
/// Expect: Dangling files deleted
|
|
||||||
Future<void> _cleanUpDanglingFiles() async {
|
|
||||||
final account = util.buildAccount();
|
|
||||||
final files = (util.FilesBuilder()
|
|
||||||
..addDir("admin")
|
|
||||||
..addJpeg("admin/test1.jpg"))
|
|
||||||
.build();
|
|
||||||
final c = DiContainer(
|
|
||||||
sqliteDb: util.buildTestDb(),
|
|
||||||
);
|
|
||||||
addTearDown(() => c.sqliteDb.close());
|
|
||||||
await c.sqliteDb.transaction(() async {
|
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
|
||||||
|
|
||||||
await c.sqliteDb.applyFuture((db) async {
|
|
||||||
await db.into(db.files).insert(sql.FilesCompanion.insert(
|
|
||||||
server: 1,
|
|
||||||
fileId: files.length,
|
|
||||||
));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(
|
|
||||||
await c.sqliteDb.select(c.sqliteDb.files).map((f) => f.fileId).get(),
|
|
||||||
[0, 1, 2],
|
|
||||||
);
|
|
||||||
await c.sqliteDb.use((db) async {
|
|
||||||
await db.cleanUpDanglingFiles();
|
|
||||||
});
|
|
||||||
expect(
|
|
||||||
await c.sqliteDb.select(c.sqliteDb.files).map((f) => f.fileId).get(),
|
|
||||||
[0, 1],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Truncate the db
|
/// Truncate the db
|
||||||
///
|
///
|
||||||
/// Expect: All tables emptied;
|
/// Expect: All tables emptied;
|
||||||
|
@ -289,11 +250,11 @@ Future<void> _truncate() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
496
app/test/test_compat_util.dart
Normal file
496
app/test/test_compat_util.dart
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
part of 'test_util.dart';
|
||||||
|
|
||||||
|
extension DiContainerExtension on DiContainer {
|
||||||
|
// ignore: deprecated_member_use
|
||||||
|
compat.SqliteDb get sqliteDb => (npDb as NpDbSqlite).compatDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ByAccount {
|
||||||
|
const _ByAccount.sql(compat.Account account) : this._(sqlAccount: account);
|
||||||
|
|
||||||
|
// const _ByAccount.app(Account account) : this._(appAccount: account);
|
||||||
|
|
||||||
|
const _ByAccount._({
|
||||||
|
this.sqlAccount,
|
||||||
|
this.appAccount,
|
||||||
|
}) : assert((sqlAccount != null) != (appAccount != null));
|
||||||
|
|
||||||
|
final compat.Account? sqlAccount;
|
||||||
|
final Account? appAccount;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountFileRowIds {
|
||||||
|
const _AccountFileRowIds(
|
||||||
|
this.accountFileRowId, this.accountRowId, this.fileRowId);
|
||||||
|
|
||||||
|
final int accountFileRowId;
|
||||||
|
final int accountRowId;
|
||||||
|
final int fileRowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountFileRowIdsWithFileId {
|
||||||
|
const _AccountFileRowIdsWithFileId(
|
||||||
|
this.accountFileRowId, this.accountRowId, this.fileRowId, this.fileId);
|
||||||
|
|
||||||
|
final int accountFileRowId;
|
||||||
|
final int accountRowId;
|
||||||
|
final int fileRowId;
|
||||||
|
final int fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on compat.SqliteDb {
|
||||||
|
/// Query AccountFiles, Accounts and Files row ID by app File
|
||||||
|
///
|
||||||
|
/// Only one of [sqlAccount] and [appAccount] must be passed
|
||||||
|
Future<_AccountFileRowIds?> accountFileRowIdsOfOrNull(
|
||||||
|
FileDescriptor file, {
|
||||||
|
compat.Account? sqlAccount,
|
||||||
|
Account? appAccount,
|
||||||
|
}) {
|
||||||
|
assert((sqlAccount != null) != (appAccount != null));
|
||||||
|
final query = queryFiles().let((q) {
|
||||||
|
q.setQueryMode(_FilesQueryMode.expression, expressions: [
|
||||||
|
accountFiles.rowId,
|
||||||
|
accountFiles.account,
|
||||||
|
accountFiles.file,
|
||||||
|
]);
|
||||||
|
if (sqlAccount != null) {
|
||||||
|
q.setSqlAccount(sqlAccount);
|
||||||
|
} else {
|
||||||
|
q.setAppAccount(appAccount!);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
q.byFileId(file.fdId);
|
||||||
|
} catch (_) {
|
||||||
|
q.byRelativePath(file.strippedPathWithEmpty);
|
||||||
|
}
|
||||||
|
return q.build()..limit(1);
|
||||||
|
});
|
||||||
|
return query
|
||||||
|
.map((r) => _AccountFileRowIds(
|
||||||
|
r.read(accountFiles.rowId)!,
|
||||||
|
r.read(accountFiles.account)!,
|
||||||
|
r.read(accountFiles.file)!,
|
||||||
|
))
|
||||||
|
.getSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See [accountFileRowIdsOfOrNull]
|
||||||
|
Future<_AccountFileRowIds> accountFileRowIdsOf(
|
||||||
|
FileDescriptor file, {
|
||||||
|
compat.Account? sqlAccount,
|
||||||
|
Account? appAccount,
|
||||||
|
}) =>
|
||||||
|
accountFileRowIdsOfOrNull(file,
|
||||||
|
sqlAccount: sqlAccount, appAccount: appAccount)
|
||||||
|
.notNull();
|
||||||
|
|
||||||
|
/// Query AccountFiles, Accounts and Files row ID by fileIds
|
||||||
|
///
|
||||||
|
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
||||||
|
Future<List<_AccountFileRowIdsWithFileId>> accountFileRowIdsByFileIds(
|
||||||
|
_ByAccount account, Iterable<int> fileIds) {
|
||||||
|
final query = queryFiles().let((q) {
|
||||||
|
q.setQueryMode(_FilesQueryMode.expression, expressions: [
|
||||||
|
accountFiles.rowId,
|
||||||
|
accountFiles.account,
|
||||||
|
accountFiles.file,
|
||||||
|
files.fileId,
|
||||||
|
]);
|
||||||
|
if (account.sqlAccount != null) {
|
||||||
|
q.setSqlAccount(account.sqlAccount!);
|
||||||
|
} else {
|
||||||
|
q.setAppAccount(account.appAccount!);
|
||||||
|
}
|
||||||
|
q.byFileIds(fileIds);
|
||||||
|
return q.build();
|
||||||
|
});
|
||||||
|
return query
|
||||||
|
.map((r) => _AccountFileRowIdsWithFileId(
|
||||||
|
r.read(accountFiles.rowId)!,
|
||||||
|
r.read(accountFiles.account)!,
|
||||||
|
r.read(accountFiles.file)!,
|
||||||
|
r.read(files.fileId)!,
|
||||||
|
))
|
||||||
|
.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
_FilesQueryBuilder queryFiles() => _FilesQueryBuilder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SqliteAlbumConverter {
|
||||||
|
static Album fromSql(
|
||||||
|
compat.Album album, File albumFile, List<compat.AlbumShare> shares) {
|
||||||
|
return Album(
|
||||||
|
lastUpdated: album.lastUpdated,
|
||||||
|
name: album.name,
|
||||||
|
provider: AlbumProvider.fromJson({
|
||||||
|
"type": album.providerType,
|
||||||
|
"content": jsonDecode(album.providerContent),
|
||||||
|
}),
|
||||||
|
coverProvider: AlbumCoverProvider.fromJson({
|
||||||
|
"type": album.coverProviderType,
|
||||||
|
"content": jsonDecode(album.coverProviderContent),
|
||||||
|
}),
|
||||||
|
sortProvider: AlbumSortProvider.fromJson({
|
||||||
|
"type": album.sortProviderType,
|
||||||
|
"content": jsonDecode(album.sortProviderContent),
|
||||||
|
}),
|
||||||
|
shares: shares.isEmpty
|
||||||
|
? null
|
||||||
|
: shares
|
||||||
|
.map((e) => AlbumShare(
|
||||||
|
userId: e.userId.toCi(),
|
||||||
|
displayName: e.displayName,
|
||||||
|
sharedAt: e.sharedAt.toUtc(),
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
// replace with the original etag when this album was cached
|
||||||
|
albumFile: albumFile.copyWith(etag: OrNull(album.fileEtag)),
|
||||||
|
savedVersion: album.version,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static compat.CompleteAlbumCompanion toSql(
|
||||||
|
Album album, int albumFileRowId, String albumFileEtag) {
|
||||||
|
final providerJson = album.provider.toJson();
|
||||||
|
final coverProviderJson = album.coverProvider.toJson();
|
||||||
|
final sortProviderJson = album.sortProvider.toJson();
|
||||||
|
final dbAlbum = compat.AlbumsCompanion.insert(
|
||||||
|
file: albumFileRowId,
|
||||||
|
fileEtag: sql.Value(albumFileEtag),
|
||||||
|
version: Album.version,
|
||||||
|
lastUpdated: album.lastUpdated,
|
||||||
|
name: album.name,
|
||||||
|
providerType: providerJson["type"],
|
||||||
|
providerContent: jsonEncode(providerJson["content"]),
|
||||||
|
coverProviderType: coverProviderJson["type"],
|
||||||
|
coverProviderContent: jsonEncode(coverProviderJson["content"]),
|
||||||
|
sortProviderType: sortProviderJson["type"],
|
||||||
|
sortProviderContent: jsonEncode(sortProviderJson["content"]),
|
||||||
|
);
|
||||||
|
final dbAlbumShares = album.shares
|
||||||
|
?.map((s) => compat.AlbumSharesCompanion(
|
||||||
|
userId: sql.Value(s.userId.toCaseInsensitiveString()),
|
||||||
|
displayName: sql.Value(s.displayName),
|
||||||
|
sharedAt: sql.Value(s.sharedAt),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
return compat.CompleteAlbumCompanion(dbAlbum, 1, dbAlbumShares ?? []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SqliteFileConverter {
|
||||||
|
static File fromSql(String userId, compat.CompleteFile f) {
|
||||||
|
final metadata = f.image?.let((obj) => Metadata(
|
||||||
|
lastUpdated: obj.lastUpdated,
|
||||||
|
fileEtag: obj.fileEtag,
|
||||||
|
imageWidth: obj.width,
|
||||||
|
imageHeight: obj.height,
|
||||||
|
exif: obj.exifRaw?.let((e) => Exif.fromJson(jsonDecode(e))),
|
||||||
|
));
|
||||||
|
final location = f.imageLocation?.let((obj) => ImageLocation(
|
||||||
|
version: obj.version,
|
||||||
|
name: obj.name,
|
||||||
|
latitude: obj.latitude,
|
||||||
|
longitude: obj.longitude,
|
||||||
|
countryCode: obj.countryCode,
|
||||||
|
admin1: obj.admin1,
|
||||||
|
admin2: obj.admin2,
|
||||||
|
));
|
||||||
|
return File(
|
||||||
|
path: "remote.php/dav/files/$userId/${f.accountFile.relativePath}",
|
||||||
|
contentLength: f.file.contentLength,
|
||||||
|
contentType: f.file.contentType,
|
||||||
|
etag: f.file.etag,
|
||||||
|
lastModified: f.file.lastModified,
|
||||||
|
isCollection: f.file.isCollection,
|
||||||
|
usedBytes: f.file.usedBytes,
|
||||||
|
hasPreview: f.file.hasPreview,
|
||||||
|
fileId: f.file.fileId,
|
||||||
|
isFavorite: f.accountFile.isFavorite,
|
||||||
|
ownerId: f.file.ownerId?.toCi(),
|
||||||
|
ownerDisplayName: f.file.ownerDisplayName,
|
||||||
|
trashbinFilename: f.trash?.filename,
|
||||||
|
trashbinOriginalLocation: f.trash?.originalLocation,
|
||||||
|
trashbinDeletionTime: f.trash?.deletionTime,
|
||||||
|
metadata: metadata,
|
||||||
|
isArchived: f.accountFile.isArchived,
|
||||||
|
overrideDateTime: f.accountFile.overrideDateTime,
|
||||||
|
location: location,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static compat.CompleteFileCompanion toSql(
|
||||||
|
compat.Account? account, File file) {
|
||||||
|
final dbFile = compat.FilesCompanion(
|
||||||
|
server: account == null
|
||||||
|
? const sql.Value.absent()
|
||||||
|
: sql.Value(account.server),
|
||||||
|
fileId: sql.Value(file.fileId!),
|
||||||
|
contentLength: sql.Value(file.contentLength),
|
||||||
|
contentType: sql.Value(file.contentType),
|
||||||
|
etag: sql.Value(file.etag),
|
||||||
|
lastModified: sql.Value(file.lastModified),
|
||||||
|
isCollection: sql.Value(file.isCollection),
|
||||||
|
usedBytes: sql.Value(file.usedBytes),
|
||||||
|
hasPreview: sql.Value(file.hasPreview),
|
||||||
|
ownerId: sql.Value(file.ownerId!.toCaseInsensitiveString()),
|
||||||
|
ownerDisplayName: sql.Value(file.ownerDisplayName),
|
||||||
|
);
|
||||||
|
final dbAccountFile = compat.AccountFilesCompanion(
|
||||||
|
account:
|
||||||
|
account == null ? const sql.Value.absent() : sql.Value(account.rowId),
|
||||||
|
relativePath: sql.Value(file.strippedPathWithEmpty),
|
||||||
|
isFavorite: sql.Value(file.isFavorite),
|
||||||
|
isArchived: sql.Value(file.isArchived),
|
||||||
|
overrideDateTime: sql.Value(file.overrideDateTime),
|
||||||
|
bestDateTime: sql.Value(file.bestDateTime),
|
||||||
|
);
|
||||||
|
final dbImage = file.metadata?.let((m) => compat.ImagesCompanion.insert(
|
||||||
|
lastUpdated: m.lastUpdated,
|
||||||
|
fileEtag: sql.Value(m.fileEtag),
|
||||||
|
width: sql.Value(m.imageWidth),
|
||||||
|
height: sql.Value(m.imageHeight),
|
||||||
|
exifRaw: sql.Value(m.exif?.toJson().let((j) => jsonEncode(j))),
|
||||||
|
dateTimeOriginal: sql.Value(m.exif?.dateTimeOriginal),
|
||||||
|
));
|
||||||
|
final dbImageLocation =
|
||||||
|
file.location?.let((l) => compat.ImageLocationsCompanion.insert(
|
||||||
|
version: l.version,
|
||||||
|
name: sql.Value(l.name),
|
||||||
|
latitude: sql.Value(l.latitude),
|
||||||
|
longitude: sql.Value(l.longitude),
|
||||||
|
countryCode: sql.Value(l.countryCode),
|
||||||
|
admin1: sql.Value(l.admin1),
|
||||||
|
admin2: sql.Value(l.admin2),
|
||||||
|
));
|
||||||
|
final dbTrash = file.trashbinDeletionTime == null
|
||||||
|
? null
|
||||||
|
: compat.TrashesCompanion.insert(
|
||||||
|
filename: file.trashbinFilename!,
|
||||||
|
originalLocation: file.trashbinOriginalLocation!,
|
||||||
|
deletionTime: file.trashbinDeletionTime!,
|
||||||
|
);
|
||||||
|
return compat.CompleteFileCompanion(
|
||||||
|
dbFile, dbAccountFile, dbImage, dbImageLocation, dbTrash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum _FilesQueryMode {
|
||||||
|
file,
|
||||||
|
completeFile,
|
||||||
|
expression,
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef _FilesQueryRelativePathBuilder = sql.Expression<bool> Function(
|
||||||
|
sql.GeneratedColumn<String> relativePath);
|
||||||
|
|
||||||
|
/// Build a Files table query
|
||||||
|
///
|
||||||
|
/// If you call more than one by* methods, the condition will be added up
|
||||||
|
/// instead of replaced. No validations will be made to make sure the resulting
|
||||||
|
/// conditions make sense
|
||||||
|
class _FilesQueryBuilder {
|
||||||
|
_FilesQueryBuilder(this.db);
|
||||||
|
|
||||||
|
/// Set the query mode
|
||||||
|
///
|
||||||
|
/// If [mode] == FilesQueryMode.expression, [expressions] must be defined and
|
||||||
|
/// not empty
|
||||||
|
void setQueryMode(
|
||||||
|
_FilesQueryMode mode, {
|
||||||
|
Iterable<sql.Expression>? expressions,
|
||||||
|
}) {
|
||||||
|
assert((mode == _FilesQueryMode.expression) !=
|
||||||
|
(expressions?.isEmpty != false));
|
||||||
|
_queryMode = mode;
|
||||||
|
_selectExpressions = expressions;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setSqlAccount(compat.Account account) {
|
||||||
|
assert(_appAccount == null);
|
||||||
|
_sqlAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAppAccount(Account account) {
|
||||||
|
assert(_sqlAccount == null);
|
||||||
|
_appAccount = account;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setAccountless() {
|
||||||
|
assert(_sqlAccount == null && _appAccount == null);
|
||||||
|
_isAccountless = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byRowId(int rowId) {
|
||||||
|
_byRowId = rowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byFileId(int fileId) {
|
||||||
|
_byFileId = fileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byFileIds(Iterable<int> fileIds) {
|
||||||
|
_byFileIds = fileIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byRelativePath(String path) {
|
||||||
|
_byRelativePath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byOrRelativePath(String path) {
|
||||||
|
_byOrRelativePathBuilder((relativePath) => relativePath.equals(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
void byOrRelativePathPattern(String pattern) {
|
||||||
|
_byOrRelativePathBuilder((relativePath) => relativePath.like(pattern));
|
||||||
|
}
|
||||||
|
|
||||||
|
void byMimePattern(String pattern) {
|
||||||
|
(_byMimePatterns ??= []).add(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
void byFavorite(bool favorite) {
|
||||||
|
_byFavorite = favorite;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byDirRowId(int dirRowId) {
|
||||||
|
_byDirRowId = dirRowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byServerRowId(int serverRowId) {
|
||||||
|
_byServerRowId = serverRowId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void byLocation(String location) {
|
||||||
|
_byLocation = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
sql.JoinedSelectStatement build() {
|
||||||
|
if (_sqlAccount == null && _appAccount == null && !_isAccountless) {
|
||||||
|
throw StateError("Invalid query: missing account");
|
||||||
|
}
|
||||||
|
final dynamic select = _queryMode == _FilesQueryMode.expression
|
||||||
|
? db.selectOnly(db.files)
|
||||||
|
: db.select(db.files);
|
||||||
|
final query = select.join([
|
||||||
|
sql.innerJoin(
|
||||||
|
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId),
|
||||||
|
useColumns: _queryMode == _FilesQueryMode.completeFile),
|
||||||
|
if (_appAccount != null) ...[
|
||||||
|
sql.innerJoin(
|
||||||
|
db.accounts, db.accounts.rowId.equalsExp(db.accountFiles.account),
|
||||||
|
useColumns: false),
|
||||||
|
sql.innerJoin(
|
||||||
|
db.servers, db.servers.rowId.equalsExp(db.accounts.server),
|
||||||
|
useColumns: false),
|
||||||
|
],
|
||||||
|
if (_byDirRowId != null)
|
||||||
|
sql.innerJoin(db.dirFiles, db.dirFiles.child.equalsExp(db.files.rowId),
|
||||||
|
useColumns: false),
|
||||||
|
if (_queryMode == _FilesQueryMode.completeFile) ...[
|
||||||
|
sql.leftOuterJoin(
|
||||||
|
db.images, db.images.accountFile.equalsExp(db.accountFiles.rowId)),
|
||||||
|
sql.leftOuterJoin(db.imageLocations,
|
||||||
|
db.imageLocations.accountFile.equalsExp(db.accountFiles.rowId)),
|
||||||
|
sql.leftOuterJoin(
|
||||||
|
db.trashes, db.trashes.file.equalsExp(db.files.rowId)),
|
||||||
|
],
|
||||||
|
]) as sql.JoinedSelectStatement;
|
||||||
|
if (_queryMode == _FilesQueryMode.expression) {
|
||||||
|
query.addColumns(_selectExpressions!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_sqlAccount != null) {
|
||||||
|
query.where(db.accountFiles.account.equals(_sqlAccount!.rowId));
|
||||||
|
} else if (_appAccount != null) {
|
||||||
|
query
|
||||||
|
..where(db.servers.address.equals(_appAccount!.url))
|
||||||
|
..where(db.accounts.userId
|
||||||
|
.equals(_appAccount!.userId.toCaseInsensitiveString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_byRowId != null) {
|
||||||
|
query.where(db.files.rowId.equals(_byRowId!));
|
||||||
|
}
|
||||||
|
if (_byFileId != null) {
|
||||||
|
query.where(db.files.fileId.equals(_byFileId!));
|
||||||
|
}
|
||||||
|
if (_byFileIds != null) {
|
||||||
|
query.where(db.files.fileId.isIn(_byFileIds!));
|
||||||
|
}
|
||||||
|
if (_byRelativePath != null) {
|
||||||
|
query.where(db.accountFiles.relativePath.equals(_byRelativePath!));
|
||||||
|
}
|
||||||
|
if (_byOrRelativePathBuilders?.isNotEmpty == true) {
|
||||||
|
final expression = _byOrRelativePathBuilders!
|
||||||
|
.sublist(1)
|
||||||
|
.fold<sql.Expression<bool>>(
|
||||||
|
_byOrRelativePathBuilders,
|
||||||
|
(previousValue, builder) =>
|
||||||
|
previousValue | builder(db.accountFiles.relativePath));
|
||||||
|
query.where(expression);
|
||||||
|
}
|
||||||
|
if (_byMimePatterns?.isNotEmpty == true) {
|
||||||
|
final expression = _byMimePatterns!.sublist(1).fold<sql.Expression<bool>>(
|
||||||
|
db.files.contentType.like(_byMimePatterns![0]),
|
||||||
|
(previousValue, element) =>
|
||||||
|
previousValue | db.files.contentType.like(element));
|
||||||
|
query.where(expression);
|
||||||
|
}
|
||||||
|
if (_byFavorite != null) {
|
||||||
|
if (_byFavorite!) {
|
||||||
|
query.where(db.accountFiles.isFavorite.equals(true));
|
||||||
|
} else {
|
||||||
|
// null are treated as false
|
||||||
|
query.where(db.accountFiles.isFavorite.equals(true).not());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_byDirRowId != null) {
|
||||||
|
query.where(db.dirFiles.dir.equals(_byDirRowId!));
|
||||||
|
}
|
||||||
|
if (_byServerRowId != null) {
|
||||||
|
query.where(db.files.server.equals(_byServerRowId!));
|
||||||
|
}
|
||||||
|
if (_byLocation != null) {
|
||||||
|
var clause = db.imageLocations.name.like(_byLocation!) |
|
||||||
|
db.imageLocations.admin1.like(_byLocation!) |
|
||||||
|
db.imageLocations.admin2.like(_byLocation!);
|
||||||
|
final countryCode = nameToAlpha2Code(_byLocation!.toCi());
|
||||||
|
if (countryCode != null) {
|
||||||
|
clause = clause | db.imageLocations.countryCode.equals(countryCode);
|
||||||
|
} else if (_byLocation!.length == 2 &&
|
||||||
|
alpha2CodeToName(_byLocation!.toUpperCase()) != null) {
|
||||||
|
clause = clause |
|
||||||
|
db.imageLocations.countryCode.equals(_byLocation!.toUpperCase());
|
||||||
|
}
|
||||||
|
query.where(clause);
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _byOrRelativePathBuilder(_FilesQueryRelativePathBuilder builder) {
|
||||||
|
(_byOrRelativePathBuilders ??= []).add(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
final compat.SqliteDb db;
|
||||||
|
|
||||||
|
_FilesQueryMode _queryMode = _FilesQueryMode.file;
|
||||||
|
Iterable<sql.Expression>? _selectExpressions;
|
||||||
|
|
||||||
|
compat.Account? _sqlAccount;
|
||||||
|
Account? _appAccount;
|
||||||
|
bool _isAccountless = false;
|
||||||
|
|
||||||
|
int? _byRowId;
|
||||||
|
int? _byFileId;
|
||||||
|
Iterable<int>? _byFileIds;
|
||||||
|
String? _byRelativePath;
|
||||||
|
List<_FilesQueryRelativePathBuilder>? _byOrRelativePathBuilders;
|
||||||
|
List<String>? _byMimePatterns;
|
||||||
|
bool? _byFavorite;
|
||||||
|
int? _byDirRowId;
|
||||||
|
int? _byServerRowId;
|
||||||
|
String? _byLocation;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:drift/drift.dart' as sql;
|
import 'package:drift/drift.dart' as sql;
|
||||||
import 'package:drift/native.dart' as sql;
|
import 'package:drift/native.dart' as sql;
|
||||||
|
@ -5,22 +7,31 @@ import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.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/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
|
import 'package:nc_photos/entity/exif.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/share.dart';
|
import 'package:nc_photos/entity/share.dart';
|
||||||
import 'package:nc_photos/entity/sharee.dart';
|
import 'package:nc_photos/entity/sharee.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
import 'package:np_async/np_async.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_common/object_util.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db/np_db.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
|
import 'package:np_geocoder/np_geocoder.dart';
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
|
part 'test_compat_util.dart';
|
||||||
|
|
||||||
class FilesBuilder {
|
class FilesBuilder {
|
||||||
FilesBuilder({
|
FilesBuilder({
|
||||||
int initialFileId = 0,
|
int initialFileId = 0,
|
||||||
|
@ -271,8 +282,8 @@ class SqlAccountWithServer with EquatableMixin {
|
||||||
@override
|
@override
|
||||||
get props => [server, account];
|
get props => [server, account];
|
||||||
|
|
||||||
final sql.Server server;
|
final compat.Server server;
|
||||||
final sql.Account account;
|
final compat.Account account;
|
||||||
}
|
}
|
||||||
|
|
||||||
void initLog() {
|
void initLog() {
|
||||||
|
@ -436,18 +447,22 @@ Sharee buildSharee({
|
||||||
shareWith: shareWith,
|
shareWith: shareWith,
|
||||||
);
|
);
|
||||||
|
|
||||||
sql.SqliteDb buildTestDb() {
|
NpDb buildTestDb() {
|
||||||
sql.driftRuntimeOptions.debugPrint = _debugPrintSql;
|
final db = NpDbSqlite();
|
||||||
return sql.SqliteDb(
|
db.initWithDb(
|
||||||
executor: sql.NativeDatabase.memory(
|
db: compat.SqliteDb(
|
||||||
logStatements: true,
|
executor: sql.NativeDatabase.memory(
|
||||||
|
logStatements: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
sql.driftRuntimeOptions.debugPrint = _debugPrintSql;
|
||||||
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertFiles(
|
Future<void> insertFiles(
|
||||||
sql.SqliteDb db, Account account, Iterable<File> files) async {
|
compat.SqliteDb db, Account account, Iterable<File> files) async {
|
||||||
final dbAccount = await db.accountOf(account);
|
final dbAccount = await db.accountOf(compat.ByAccount.db(account.toDb()));
|
||||||
for (final f in files) {
|
for (final f in files) {
|
||||||
final sharedQuery = db.selectOnly(db.files).join([
|
final sharedQuery = db.selectOnly(db.files).join([
|
||||||
sql.innerJoin(
|
sql.innerJoin(
|
||||||
|
@ -459,7 +474,7 @@ Future<void> insertFiles(
|
||||||
..where(db.files.fileId.equals(f.fileId!));
|
..where(db.files.fileId.equals(f.fileId!));
|
||||||
var rowId = (await sharedQuery.map((r) => r.read(db.files.rowId)).get())
|
var rowId = (await sharedQuery.map((r) => r.read(db.files.rowId)).get())
|
||||||
.firstOrNull;
|
.firstOrNull;
|
||||||
final insert = SqliteFileConverter.toSql(dbAccount, f);
|
final insert = _SqliteFileConverter.toSql(dbAccount, f);
|
||||||
if (rowId == null) {
|
if (rowId == null) {
|
||||||
final dbFile = await db.into(db.files).insertReturning(insert.file);
|
final dbFile = await db.into(db.files).insertReturning(insert.file);
|
||||||
rowId = dbFile.rowId;
|
rowId = dbFile.rowId;
|
||||||
|
@ -483,18 +498,18 @@ Future<void> insertFiles(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertDirRelation(
|
Future<void> insertDirRelation(compat.SqliteDb db, Account account, File dir,
|
||||||
sql.SqliteDb db, Account account, File dir, Iterable<File> children) async {
|
Iterable<File> children) async {
|
||||||
final dbAccount = await db.accountOf(account);
|
final dbAccount = await db.accountOf(compat.ByAccount.db(account.toDb()));
|
||||||
final dirRowIds = (await db.accountFileRowIdsByFileIds(
|
final dirRowIds = (await db
|
||||||
sql.ByAccount.sql(dbAccount), [dir.fileId!]))
|
.accountFileRowIdsByFileIds(_ByAccount.sql(dbAccount), [dir.fileId!]))
|
||||||
.first;
|
.first;
|
||||||
final childRowIds = await db.accountFileRowIdsByFileIds(
|
final childRowIds = await db.accountFileRowIdsByFileIds(
|
||||||
sql.ByAccount.sql(dbAccount), [dir, ...children].map((f) => f.fileId!));
|
_ByAccount.sql(dbAccount), [dir, ...children].map((f) => f.fileId!));
|
||||||
await db.batch((batch) {
|
await db.batch((batch) {
|
||||||
batch.insertAll(
|
batch.insertAll(
|
||||||
db.dirFiles,
|
db.dirFiles,
|
||||||
childRowIds.map((c) => sql.DirFilesCompanion.insert(
|
childRowIds.map((c) => compat.DirFilesCompanion.insert(
|
||||||
dir: dirRowIds.fileRowId,
|
dir: dirRowIds.fileRowId,
|
||||||
child: c.fileRowId,
|
child: c.fileRowId,
|
||||||
)),
|
)),
|
||||||
|
@ -503,15 +518,15 @@ Future<void> insertDirRelation(
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> insertAlbums(
|
Future<void> insertAlbums(
|
||||||
sql.SqliteDb db, Account account, Iterable<Album> albums) async {
|
compat.SqliteDb db, Account account, Iterable<Album> albums) async {
|
||||||
final dbAccount = await db.accountOf(account);
|
final dbAccount = await db.accountOf(compat.ByAccount.db(account.toDb()));
|
||||||
for (final a in albums) {
|
for (final a in albums) {
|
||||||
final rowIds =
|
final rowIds =
|
||||||
await db.accountFileRowIdsOf(a.albumFile!, sqlAccount: dbAccount);
|
await db.accountFileRowIdsOf(a.albumFile!, sqlAccount: dbAccount);
|
||||||
final insert =
|
final insert =
|
||||||
SqliteAlbumConverter.toSql(a, rowIds.fileRowId, a.albumFile!.etag!);
|
_SqliteAlbumConverter.toSql(a, rowIds.fileRowId, a.albumFile!.etag!);
|
||||||
final dbAlbum = await db.into(db.albums).insertReturning(insert.album);
|
final dbAlbum = await db.into(db.albums).insertReturning(insert.album);
|
||||||
for (final s in insert.albumShares) {
|
for (final s in insert.shares) {
|
||||||
await db
|
await db
|
||||||
.into(db.albumShares)
|
.into(db.albumShares)
|
||||||
.insert(s.copyWith(album: sql.Value(dbAlbum.rowId)));
|
.insert(s.copyWith(album: sql.Value(dbAlbum.rowId)));
|
||||||
|
@ -519,7 +534,7 @@ Future<void> insertAlbums(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<File>> listSqliteDbFiles(sql.SqliteDb db) async {
|
Future<Set<File>> listSqliteDbFiles(compat.SqliteDb db) async {
|
||||||
final query = db.select(db.files).join([
|
final query = db.select(db.files).join([
|
||||||
sql.innerJoin(
|
sql.innerJoin(
|
||||||
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
||||||
|
@ -532,9 +547,9 @@ Future<Set<File>> listSqliteDbFiles(sql.SqliteDb db) async {
|
||||||
sql.leftOuterJoin(db.trashes, db.trashes.file.equalsExp(db.files.rowId)),
|
sql.leftOuterJoin(db.trashes, db.trashes.file.equalsExp(db.files.rowId)),
|
||||||
]);
|
]);
|
||||||
return (await query
|
return (await query
|
||||||
.map((r) => SqliteFileConverter.fromSql(
|
.map((r) => _SqliteFileConverter.fromSql(
|
||||||
r.readTable(db.accounts).userId,
|
r.readTable(db.accounts).userId,
|
||||||
sql.CompleteFile(
|
compat.CompleteFile(
|
||||||
r.readTable(db.files),
|
r.readTable(db.files),
|
||||||
r.readTable(db.accountFiles),
|
r.readTable(db.accountFiles),
|
||||||
r.readTableOrNull(db.images),
|
r.readTableOrNull(db.images),
|
||||||
|
@ -546,7 +561,7 @@ Future<Set<File>> listSqliteDbFiles(sql.SqliteDb db) async {
|
||||||
.toSet();
|
.toSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<File, Set<File>>> listSqliteDbDirs(sql.SqliteDb db) async {
|
Future<Map<File, Set<File>>> listSqliteDbDirs(compat.SqliteDb db) async {
|
||||||
final query = db.select(db.files).join([
|
final query = db.select(db.files).join([
|
||||||
sql.innerJoin(
|
sql.innerJoin(
|
||||||
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
||||||
|
@ -559,7 +574,7 @@ Future<Map<File, Set<File>>> listSqliteDbDirs(sql.SqliteDb db) async {
|
||||||
sql.leftOuterJoin(db.trashes, db.trashes.file.equalsExp(db.files.rowId)),
|
sql.leftOuterJoin(db.trashes, db.trashes.file.equalsExp(db.files.rowId)),
|
||||||
]);
|
]);
|
||||||
final fileMap = Map.fromEntries(await query.map((r) {
|
final fileMap = Map.fromEntries(await query.map((r) {
|
||||||
final f = sql.CompleteFile(
|
final f = compat.CompleteFile(
|
||||||
r.readTable(db.files),
|
r.readTable(db.files),
|
||||||
r.readTable(db.accountFiles),
|
r.readTable(db.accountFiles),
|
||||||
r.readTableOrNull(db.images),
|
r.readTableOrNull(db.images),
|
||||||
|
@ -568,7 +583,7 @@ Future<Map<File, Set<File>>> listSqliteDbDirs(sql.SqliteDb db) async {
|
||||||
);
|
);
|
||||||
return MapEntry(
|
return MapEntry(
|
||||||
f.file.rowId,
|
f.file.rowId,
|
||||||
SqliteFileConverter.fromSql(r.readTable(db.accounts).userId, f),
|
_SqliteFileConverter.fromSql(r.readTable(db.accounts).userId, f),
|
||||||
);
|
);
|
||||||
}).get());
|
}).get());
|
||||||
|
|
||||||
|
@ -581,7 +596,7 @@ Future<Map<File, Set<File>>> listSqliteDbDirs(sql.SqliteDb db) async {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<Album>> listSqliteDbAlbums(sql.SqliteDb db) async {
|
Future<Set<Album>> listSqliteDbAlbums(compat.SqliteDb db) async {
|
||||||
final albumQuery = db.select(db.albums).join([
|
final albumQuery = db.select(db.albums).join([
|
||||||
sql.innerJoin(db.files, db.files.rowId.equalsExp(db.albums.file)),
|
sql.innerJoin(db.files, db.files.rowId.equalsExp(db.albums.file)),
|
||||||
sql.innerJoin(
|
sql.innerJoin(
|
||||||
|
@ -590,9 +605,9 @@ Future<Set<Album>> listSqliteDbAlbums(sql.SqliteDb db) async {
|
||||||
db.accounts, db.accounts.rowId.equalsExp(db.accountFiles.account)),
|
db.accounts, db.accounts.rowId.equalsExp(db.accountFiles.account)),
|
||||||
]);
|
]);
|
||||||
final albums = await albumQuery.map((r) {
|
final albums = await albumQuery.map((r) {
|
||||||
final albumFile = SqliteFileConverter.fromSql(
|
final albumFile = _SqliteFileConverter.fromSql(
|
||||||
r.readTable(db.accounts).userId,
|
r.readTable(db.accounts).userId,
|
||||||
sql.CompleteFile(
|
compat.CompleteFile(
|
||||||
r.readTable(db.files),
|
r.readTable(db.files),
|
||||||
r.readTable(db.accountFiles),
|
r.readTable(db.accountFiles),
|
||||||
null,
|
null,
|
||||||
|
@ -602,7 +617,7 @@ Future<Set<Album>> listSqliteDbAlbums(sql.SqliteDb db) async {
|
||||||
);
|
);
|
||||||
return Tuple2(
|
return Tuple2(
|
||||||
r.read(db.albums.rowId)!,
|
r.read(db.albums.rowId)!,
|
||||||
SqliteAlbumConverter.fromSql(r.readTable(db.albums), albumFile, []),
|
_SqliteAlbumConverter.fromSql(r.readTable(db.albums), albumFile, []),
|
||||||
);
|
);
|
||||||
}).get();
|
}).get();
|
||||||
|
|
||||||
|
@ -627,7 +642,7 @@ Future<Set<Album>> listSqliteDbAlbums(sql.SqliteDb db) async {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<SqlAccountWithServer>> listSqliteDbServerAccounts(
|
Future<Set<SqlAccountWithServer>> listSqliteDbServerAccounts(
|
||||||
sql.SqliteDb db) async {
|
compat.SqliteDb db) async {
|
||||||
final query = db.select(db.servers).join([
|
final query = db.select(db.servers).join([
|
||||||
sql.leftOuterJoin(
|
sql.leftOuterJoin(
|
||||||
db.accounts, db.accounts.server.equalsExp(db.servers.rowId)),
|
db.accounts, db.accounts.server.equalsExp(db.servers.rowId)),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
|
@ -10,8 +11,8 @@ import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/file.dart';
|
import 'package:nc_photos/entity/file.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/pref/provider/memory.dart';
|
import 'package:nc_photos/entity/pref/provider/memory.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/album/add_file_to_album.dart';
|
import 'package:nc_photos/use_case/album/add_file_to_album.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:np_string/np_string.dart';
|
import 'package:np_string/np_string.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
@ -50,12 +51,12 @@ Future<void> _addFile() async {
|
||||||
fileRepo: MockFileMemoryRepo(),
|
fileRepo: MockFileMemoryRepo(),
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, [file]);
|
await util.insertFiles(c.sqliteDb, account, [file]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -116,12 +117,12 @@ Future<void> _addExistingFile() async {
|
||||||
fileRepo: MockFileMemoryRepo(),
|
fileRepo: MockFileMemoryRepo(),
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -194,13 +195,13 @@ Future<void> _addExistingSharedFile() async {
|
||||||
fileRepo: MockFileMemoryRepo(),
|
fileRepo: MockFileMemoryRepo(),
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
});
|
});
|
||||||
|
@ -252,14 +253,14 @@ Future<void> _addFileToSharedAlbumOwned() async {
|
||||||
shareRepo: MockShareMemoryRepo([
|
shareRepo: MockShareMemoryRepo([
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider({
|
pref: Pref.scoped(PrefMemoryProvider({
|
||||||
"isLabEnableSharedAlbum": true,
|
"isLabEnableSharedAlbum": true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, [file]);
|
await util.insertFiles(c.sqliteDb, account, [file]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -295,14 +296,14 @@ Future<void> _addFileOwnedByUserToSharedAlbumOwned() async {
|
||||||
shareRepo: MockShareMemoryRepo([
|
shareRepo: MockShareMemoryRepo([
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider({
|
pref: Pref.scoped(PrefMemoryProvider({
|
||||||
"isLabEnableSharedAlbum": true,
|
"isLabEnableSharedAlbum": true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, [file]);
|
await util.insertFiles(c.sqliteDb, account, [file]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -344,14 +345,14 @@ Future<void> _addFileToMultiuserSharedAlbumNotOwned() async {
|
||||||
util.buildShare(
|
util.buildShare(
|
||||||
id: "1", file: albumFile, uidOwner: "user1", shareWith: "user2"),
|
id: "1", file: albumFile, uidOwner: "user1", shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider({
|
pref: Pref.scoped(PrefMemoryProvider({
|
||||||
"isLabEnableSharedAlbum": true,
|
"isLabEnableSharedAlbum": true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, [file]);
|
await util.insertFiles(c.sqliteDb, account, [file]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,167 +1,167 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
// import 'package:drift/drift.dart' as sql;
|
||||||
import 'package:nc_photos/di_container.dart';
|
// import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_person.dart';
|
// import 'package:nc_photos/entity/face_recognition_person.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_person/data_source.dart';
|
// import 'package:nc_photos/entity/face_recognition_person/data_source.dart';
|
||||||
import 'package:nc_photos/entity/face_recognition_person/repo.dart';
|
// import 'package:nc_photos/entity/face_recognition_person/repo.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
// import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
// import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
||||||
import 'package:nc_photos/use_case/face_recognition_person/sync_face_recognition_person.dart';
|
// import 'package:nc_photos/use_case/face_recognition_person/sync_face_recognition_person.dart';
|
||||||
import 'package:test/test.dart';
|
// import 'package:test/test.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
// import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../../mock_type.dart';
|
// import '../../mock_type.dart';
|
||||||
import '../../test_util.dart' as util;
|
// import '../../test_util.dart' as util;
|
||||||
|
|
||||||
void main() {
|
// void main() {
|
||||||
group("SyncFaceRecognitionPerson", () {
|
// group("SyncFaceRecognitionPerson", () {
|
||||||
test("new", _new);
|
// test("new", _new);
|
||||||
test("remove", _remove);
|
// test("remove", _remove);
|
||||||
test("update", _update);
|
// test("update", _update);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Sync with remote where there are new persons
|
// /// Sync with remote where there are new persons
|
||||||
///
|
// ///
|
||||||
/// Remote: [test1, test2, test3]
|
// /// Remote: [test1, test2, test3]
|
||||||
/// Local: [test1]
|
// /// Local: [test1]
|
||||||
/// Expect: [test1, test2, test3]
|
// /// Expect: [test1, test2, test3]
|
||||||
Future<void> _new() async {
|
// Future<void> _new() async {
|
||||||
final account = util.buildAccount();
|
// final account = util.buildAccount();
|
||||||
final c = DiContainer.late();
|
// final c = DiContainer.late();
|
||||||
c.sqliteDb = util.buildTestDb();
|
// c.sqliteDb = util.buildTestDb();
|
||||||
addTearDown(() => c.sqliteDb.close());
|
// addTearDown(() => c.sqliteDb.close());
|
||||||
c.faceRecognitionPersonRepoRemote = MockFaceRecognitionPersonMemoryRepo({
|
// c.faceRecognitionPersonRepoRemote = MockFaceRecognitionPersonMemoryRepo({
|
||||||
account.id: [
|
// account.id: [
|
||||||
const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
// const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
||||||
const FaceRecognitionPerson(name: "test2", thumbFaceId: 2, count: 10),
|
// const FaceRecognitionPerson(name: "test2", thumbFaceId: 2, count: 10),
|
||||||
const FaceRecognitionPerson(name: "test3", thumbFaceId: 3, count: 100),
|
// const FaceRecognitionPerson(name: "test3", thumbFaceId: 3, count: 100),
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
// c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
||||||
FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
// FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
||||||
await c.sqliteDb.transaction(() async {
|
// await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
// await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.batch((batch) {
|
// await c.sqliteDb.batch((batch) {
|
||||||
batch.insert(
|
// batch.insert(
|
||||||
c.sqliteDb.faceRecognitionPersons,
|
// c.sqliteDb.faceRecognitionPersons,
|
||||||
sql.FaceRecognitionPersonsCompanion.insert(
|
// sql.FaceRecognitionPersonsCompanion.insert(
|
||||||
account: 1, name: "test1", thumbFaceId: 1, count: 1),
|
// account: 1, name: "test1", thumbFaceId: 1, count: 1),
|
||||||
);
|
// );
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
await SyncFaceRecognitionPerson(c)(account);
|
// await SyncFaceRecognitionPerson(c)(account);
|
||||||
expect(
|
// expect(
|
||||||
await _listSqliteDbPersons(c.sqliteDb),
|
// await _listSqliteDbPersons(c.sqliteDb),
|
||||||
{
|
// {
|
||||||
account.userId.toCaseInsensitiveString(): {
|
// account.userId.toCaseInsensitiveString(): {
|
||||||
const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
// const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
||||||
const FaceRecognitionPerson(name: "test2", thumbFaceId: 2, count: 10),
|
// const FaceRecognitionPerson(name: "test2", thumbFaceId: 2, count: 10),
|
||||||
const FaceRecognitionPerson(name: "test3", thumbFaceId: 3, count: 100),
|
// const FaceRecognitionPerson(name: "test3", thumbFaceId: 3, count: 100),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Sync with remote where there are removed persons
|
// /// Sync with remote where there are removed persons
|
||||||
///
|
// ///
|
||||||
/// Remote: [test1]
|
// /// Remote: [test1]
|
||||||
/// Local: [test1, test2, test3]
|
// /// Local: [test1, test2, test3]
|
||||||
/// Expect: [test1]
|
// /// Expect: [test1]
|
||||||
Future<void> _remove() async {
|
// Future<void> _remove() async {
|
||||||
final account = util.buildAccount();
|
// final account = util.buildAccount();
|
||||||
final c = DiContainer.late();
|
// final c = DiContainer.late();
|
||||||
c.sqliteDb = util.buildTestDb();
|
// c.sqliteDb = util.buildTestDb();
|
||||||
addTearDown(() => c.sqliteDb.close());
|
// addTearDown(() => c.sqliteDb.close());
|
||||||
c.faceRecognitionPersonRepoRemote = MockFaceRecognitionPersonMemoryRepo({
|
// c.faceRecognitionPersonRepoRemote = MockFaceRecognitionPersonMemoryRepo({
|
||||||
account.id: [
|
// account.id: [
|
||||||
const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
// const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
// c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
||||||
FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
// FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
||||||
await c.sqliteDb.transaction(() async {
|
// await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
// await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.batch((batch) {
|
// await c.sqliteDb.batch((batch) {
|
||||||
batch.insertAll(c.sqliteDb.faceRecognitionPersons, [
|
// batch.insertAll(c.sqliteDb.faceRecognitionPersons, [
|
||||||
sql.FaceRecognitionPersonsCompanion.insert(
|
// sql.FaceRecognitionPersonsCompanion.insert(
|
||||||
account: 1, name: "test1", thumbFaceId: 1, count: 1),
|
// account: 1, name: "test1", thumbFaceId: 1, count: 1),
|
||||||
sql.FaceRecognitionPersonsCompanion.insert(
|
// sql.FaceRecognitionPersonsCompanion.insert(
|
||||||
account: 1, name: "test2", thumbFaceId: 2, count: 10),
|
// account: 1, name: "test2", thumbFaceId: 2, count: 10),
|
||||||
sql.FaceRecognitionPersonsCompanion.insert(
|
// sql.FaceRecognitionPersonsCompanion.insert(
|
||||||
account: 1, name: "test3", thumbFaceId: 3, count: 100),
|
// account: 1, name: "test3", thumbFaceId: 3, count: 100),
|
||||||
]);
|
// ]);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
await SyncFaceRecognitionPerson(c)(account);
|
// await SyncFaceRecognitionPerson(c)(account);
|
||||||
expect(
|
// expect(
|
||||||
await _listSqliteDbPersons(c.sqliteDb),
|
// await _listSqliteDbPersons(c.sqliteDb),
|
||||||
{
|
// {
|
||||||
account.userId.toCaseInsensitiveString(): {
|
// account.userId.toCaseInsensitiveString(): {
|
||||||
const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
// const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Sync with remote where there are updated persons (i.e, same name, different
|
// /// Sync with remote where there are updated persons (i.e, same name, different
|
||||||
/// properties)
|
// /// properties)
|
||||||
///
|
// ///
|
||||||
/// Remote: [test1, test2 (face: 3)]
|
// /// Remote: [test1, test2 (face: 3)]
|
||||||
/// Local: [test1, test2 (face: 2)]
|
// /// Local: [test1, test2 (face: 2)]
|
||||||
/// Expect: [test1, test2 (face: 3)]
|
// /// Expect: [test1, test2 (face: 3)]
|
||||||
Future<void> _update() async {
|
// Future<void> _update() async {
|
||||||
final account = util.buildAccount();
|
// final account = util.buildAccount();
|
||||||
final c = DiContainer.late();
|
// final c = DiContainer.late();
|
||||||
c.sqliteDb = util.buildTestDb();
|
// c.sqliteDb = util.buildTestDb();
|
||||||
addTearDown(() => c.sqliteDb.close());
|
// addTearDown(() => c.sqliteDb.close());
|
||||||
c.faceRecognitionPersonRepoRemote = MockFaceRecognitionPersonMemoryRepo({
|
// c.faceRecognitionPersonRepoRemote = MockFaceRecognitionPersonMemoryRepo({
|
||||||
account.id: [
|
// account.id: [
|
||||||
const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
// const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
||||||
const FaceRecognitionPerson(name: "test2", thumbFaceId: 3, count: 10),
|
// const FaceRecognitionPerson(name: "test2", thumbFaceId: 3, count: 10),
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
// c.faceRecognitionPersonRepoLocal = BasicFaceRecognitionPersonRepo(
|
||||||
FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
// FaceRecognitionPersonSqliteDbDataSource(c.sqliteDb));
|
||||||
await c.sqliteDb.transaction(() async {
|
// await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
// await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.batch((batch) {
|
// await c.sqliteDb.batch((batch) {
|
||||||
batch.insertAll(c.sqliteDb.faceRecognitionPersons, [
|
// batch.insertAll(c.sqliteDb.faceRecognitionPersons, [
|
||||||
sql.FaceRecognitionPersonsCompanion.insert(
|
// sql.FaceRecognitionPersonsCompanion.insert(
|
||||||
account: 1, name: "test1", thumbFaceId: 1, count: 1),
|
// account: 1, name: "test1", thumbFaceId: 1, count: 1),
|
||||||
sql.FaceRecognitionPersonsCompanion.insert(
|
// sql.FaceRecognitionPersonsCompanion.insert(
|
||||||
account: 1, name: "test2", thumbFaceId: 2, count: 10),
|
// account: 1, name: "test2", thumbFaceId: 2, count: 10),
|
||||||
]);
|
// ]);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
await SyncFaceRecognitionPerson(c)(account);
|
// await SyncFaceRecognitionPerson(c)(account);
|
||||||
expect(
|
// expect(
|
||||||
await _listSqliteDbPersons(c.sqliteDb),
|
// await _listSqliteDbPersons(c.sqliteDb),
|
||||||
{
|
// {
|
||||||
account.userId.toCaseInsensitiveString(): {
|
// account.userId.toCaseInsensitiveString(): {
|
||||||
const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
// const FaceRecognitionPerson(name: "test1", thumbFaceId: 1, count: 1),
|
||||||
const FaceRecognitionPerson(name: "test2", thumbFaceId: 3, count: 10),
|
// const FaceRecognitionPerson(name: "test2", thumbFaceId: 3, count: 10),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<Map<String, Set<FaceRecognitionPerson>>> _listSqliteDbPersons(
|
// Future<Map<String, Set<FaceRecognitionPerson>>> _listSqliteDbPersons(
|
||||||
sql.SqliteDb db) async {
|
// sql.SqliteDb db) async {
|
||||||
final query = db.select(db.faceRecognitionPersons).join([
|
// final query = db.select(db.faceRecognitionPersons).join([
|
||||||
sql.innerJoin(db.accounts,
|
// sql.innerJoin(db.accounts,
|
||||||
db.accounts.rowId.equalsExp(db.faceRecognitionPersons.account)),
|
// db.accounts.rowId.equalsExp(db.faceRecognitionPersons.account)),
|
||||||
]);
|
// ]);
|
||||||
final result = await query
|
// final result = await query
|
||||||
.map((r) => Tuple2(
|
// .map((r) => Tuple2(
|
||||||
r.readTable(db.accounts), r.readTable(db.faceRecognitionPersons)))
|
// r.readTable(db.accounts), r.readTable(db.faceRecognitionPersons)))
|
||||||
.get();
|
// .get();
|
||||||
final product = <String, Set<FaceRecognitionPerson>>{};
|
// final product = <String, Set<FaceRecognitionPerson>>{};
|
||||||
for (final r in result) {
|
// for (final r in result) {
|
||||||
(product[r.item1.userId] ??= {})
|
// (product[r.item1.userId] ??= {})
|
||||||
.add(SqliteFaceRecognitionPersonConverter.fromSql(r.item2));
|
// .add(SqliteFaceRecognitionPersonConverter.fromSql(r.item2));
|
||||||
}
|
// }
|
||||||
return product;
|
// return product;
|
||||||
}
|
// }
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/find_file.dart';
|
import 'package:nc_photos/use_case/find_file.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../test_util.dart' as util;
|
import '../test_util.dart' as util;
|
||||||
|
@ -22,11 +23,11 @@ Future<void> _findFile() async {
|
||||||
..addJpeg("admin/test2.jpg"))
|
..addJpeg("admin/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -40,11 +41,11 @@ Future<void> _findMissingFile() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final files = (util.FilesBuilder()..addJpeg("admin/test1.jpg")).build();
|
final files = (util.FilesBuilder()..addJpeg("admin/test1.jpg")).build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import 'package:clock/clock.dart';
|
import 'package:clock/clock.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/file_descriptor.dart';
|
import 'package:nc_photos/entity/file_descriptor.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
import 'package:nc_photos/use_case/inflate_file_descriptor.dart';
|
||||||
import 'package:np_collection/np_collection.dart';
|
import 'package:np_collection/np_collection.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../test_util.dart' as util;
|
import '../test_util.dart' as util;
|
||||||
|
@ -27,11 +28,11 @@ Future<void> _one() async {
|
||||||
..addJpeg("admin/test2.jpg"))
|
..addJpeg("admin/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -59,11 +60,11 @@ Future<void> _multiple() async {
|
||||||
..addJpeg("admin/test6.jpg"))
|
..addJpeg("admin/test6.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -87,11 +88,11 @@ Future<void> _missing() async {
|
||||||
..addJpeg("admin/test2.jpg"))
|
..addJpeg("admin/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.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/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/list_location_group.dart';
|
import 'package:nc_photos/use_case/list_location_group.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../test_util.dart' as util;
|
import '../test_util.dart' as util;
|
||||||
|
@ -23,11 +24,11 @@ void main() {
|
||||||
Future<void> _empty() async {
|
Future<void> _empty() async {
|
||||||
final account = util.buildAccount();
|
final account = util.buildAccount();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
});
|
});
|
||||||
|
|
||||||
final result = await ListLocationGroup(c)(account);
|
final result = await ListLocationGroup(c)(account);
|
||||||
|
@ -48,11 +49,11 @@ Future<void> _noLocation() async {
|
||||||
..addJpeg("admin/test1.jpg"))
|
..addJpeg("admin/test1.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -90,11 +91,11 @@ Future<void> _nFile1Location() async {
|
||||||
)))
|
)))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -153,11 +154,11 @@ Future<void> _nFileNLocation() async {
|
||||||
)))
|
)))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -216,6 +217,13 @@ Future<void> _multipleRoots() async {
|
||||||
countryCode: "AD",
|
countryCode: "AD",
|
||||||
))
|
))
|
||||||
..addJpeg("admin/test2/test4.jpg",
|
..addJpeg("admin/test2/test4.jpg",
|
||||||
|
location: const ImageLocation(
|
||||||
|
name: "Some place",
|
||||||
|
latitude: 1.2,
|
||||||
|
longitude: 3.4,
|
||||||
|
countryCode: "AD",
|
||||||
|
))
|
||||||
|
..addJpeg("admin/test3/test5.jpg",
|
||||||
location: const ImageLocation(
|
location: const ImageLocation(
|
||||||
name: "Some place",
|
name: "Some place",
|
||||||
latitude: 1.2,
|
latitude: 1.2,
|
||||||
|
@ -224,11 +232,11 @@ Future<void> _multipleRoots() async {
|
||||||
)))
|
)))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/pref/provider/memory.dart';
|
import 'package:nc_photos/entity/pref/provider/memory.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/album/remove_album.dart';
|
import 'package:nc_photos/use_case/album/remove_album.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../mock_type.dart';
|
import '../mock_type.dart';
|
||||||
|
@ -36,7 +37,7 @@ Future<void> _removeAlbum() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album1, album2]),
|
albumRepo: MockAlbumMemoryRepo([album1, album2]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile1, albumFile2]),
|
fileRepo: MockFileMemoryRepo([albumFile1, albumFile2]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
@ -67,7 +68,7 @@ Future<void> _removeSharedAlbum() async {
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider({
|
pref: Pref.scoped(PrefMemoryProvider({
|
||||||
"isLabEnableSharedAlbum": true,
|
"isLabEnableSharedAlbum": true,
|
||||||
})),
|
})),
|
||||||
|
@ -109,7 +110,7 @@ Future<void> _removeSharedAlbumFileInOtherAlbum() async {
|
||||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||||
util.buildShare(id: "2", file: albumFiles[1], shareWith: "user1"),
|
util.buildShare(id: "2", file: albumFiles[1], shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider({
|
pref: Pref.scoped(PrefMemoryProvider({
|
||||||
"isLabEnableSharedAlbum": true,
|
"isLabEnableSharedAlbum": true,
|
||||||
})),
|
})),
|
||||||
|
@ -150,15 +151,15 @@ Future<void> _removeSharedAlbumResyncedFile() async {
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider({
|
pref: Pref.scoped(PrefMemoryProvider({
|
||||||
"isLabEnableSharedAlbum": true,
|
"isLabEnableSharedAlbum": true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
import 'package:nc_photos/entity/album/provider.dart';
|
import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/album/remove_from_album.dart';
|
import 'package:nc_photos/use_case/album/remove_from_album.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../mock_type.dart';
|
import '../mock_type.dart';
|
||||||
|
@ -52,11 +53,11 @@ Future<void> _removeLastFile() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile, file1]),
|
fileRepo: MockFileMemoryRepo([albumFile, file1]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -103,11 +104,11 @@ Future<void> _remove1OfNFiles() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -160,11 +161,11 @@ Future<void> _removeLatestOfNFiles() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -214,11 +215,11 @@ Future<void> _removeManualCoverFile() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
||||||
shareRepo: MockShareRepo(),
|
shareRepo: MockShareRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -268,11 +269,11 @@ Future<void> _removeFromSharedAlbumOwned() async {
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
util.buildShare(id: "1", file: file1, shareWith: "user1"),
|
util.buildShare(id: "1", file: file1, shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -314,12 +315,12 @@ Future<void> _removeFromSharedAlbumOwnedWithOtherShare() async {
|
||||||
util.buildShare(
|
util.buildShare(
|
||||||
id: "3", uidOwner: "user1", file: file1, shareWith: "user2"),
|
id: "3", uidOwner: "user1", file: file1, shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, files);
|
await util.insertFiles(c.sqliteDb, user1Account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -362,11 +363,11 @@ Future<void> _removeFromSharedAlbumOwnedLeaveExtraShare() async {
|
||||||
util.buildShare(id: "1", file: file1, shareWith: "user1"),
|
util.buildShare(id: "1", file: file1, shareWith: "user1"),
|
||||||
util.buildShare(id: "2", file: file1, shareWith: "user2"),
|
util.buildShare(id: "2", file: file1, shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -411,11 +412,11 @@ Future<void> _removeFromSharedAlbumOwnedFileInOtherAlbum() async {
|
||||||
util.buildShare(id: "2", file: files[0], shareWith: "user2"),
|
util.buildShare(id: "2", file: files[0], shareWith: "user2"),
|
||||||
util.buildShare(id: "3", file: album2File, shareWith: "user1"),
|
util.buildShare(id: "3", file: album2File, shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -457,11 +458,11 @@ Future<void> _removeFromSharedAlbumNotOwned() async {
|
||||||
util.buildShare(id: "2", file: file1, shareWith: "user1"),
|
util.buildShare(id: "2", file: file1, shareWith: "user1"),
|
||||||
util.buildShare(id: "3", file: file1, shareWith: "user2"),
|
util.buildShare(id: "3", file: file1, shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -507,11 +508,11 @@ Future<void> _removeFromSharedAlbumNotOwnedWithOwnerShare() async {
|
||||||
util.buildShare(
|
util.buildShare(
|
||||||
id: "3", uidOwner: "user1", file: file1, shareWith: "user2"),
|
id: "3", uidOwner: "user1", file: file1, shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/album.dart';
|
import 'package:nc_photos/entity/album.dart';
|
||||||
import 'package:nc_photos/entity/album/cover_provider.dart';
|
import 'package:nc_photos/entity/album/cover_provider.dart';
|
||||||
|
@ -7,9 +8,9 @@ import 'package:nc_photos/entity/album/provider.dart';
|
||||||
import 'package:nc_photos/entity/album/sort_provider.dart';
|
import 'package:nc_photos/entity/album/sort_provider.dart';
|
||||||
import 'package:nc_photos/entity/pref.dart';
|
import 'package:nc_photos/entity/pref.dart';
|
||||||
import 'package:nc_photos/entity/pref/provider/memory.dart';
|
import 'package:nc_photos/entity/pref/provider/memory.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/remove.dart';
|
import 'package:nc_photos/use_case/remove.dart';
|
||||||
import 'package:np_common/or_null.dart';
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../mock_type.dart';
|
import '../mock_type.dart';
|
||||||
|
@ -46,12 +47,12 @@ Future<void> _removeFile() async {
|
||||||
albumRepo: MockAlbumMemoryRepo(),
|
albumRepo: MockAlbumMemoryRepo(),
|
||||||
fileRepo: MockFileMemoryRepo(files),
|
fileRepo: MockFileMemoryRepo(files),
|
||||||
shareRepo: MockShareMemoryRepo(),
|
shareRepo: MockShareMemoryRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -72,12 +73,12 @@ Future<void> _removeFileNoCleanUp() async {
|
||||||
albumRepo: MockAlbumMemoryRepo(),
|
albumRepo: MockAlbumMemoryRepo(),
|
||||||
fileRepo: MockFileMemoryRepo(files),
|
fileRepo: MockFileMemoryRepo(files),
|
||||||
shareRepo: MockShareMemoryRepo(),
|
shareRepo: MockShareMemoryRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,12 +99,12 @@ Future<void> _removeAlbumFile() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
||||||
shareRepo: MockShareMemoryRepo(),
|
shareRepo: MockShareMemoryRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -142,12 +143,12 @@ Future<void> _removeAlbumFileNoCleanUp() async {
|
||||||
albumRepo: MockAlbumMemoryRepo([album]),
|
albumRepo: MockAlbumMemoryRepo([album]),
|
||||||
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
fileRepo: MockFileMemoryRepo([albumFile, ...files]),
|
||||||
shareRepo: MockShareMemoryRepo(),
|
shareRepo: MockShareMemoryRepo(),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -195,12 +196,12 @@ Future<void> _removeSharedAlbumFile() async {
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -262,13 +263,13 @@ Future<void> _removeSharedAlbumSharedFile() async {
|
||||||
id: "2", file: user1Files[0], uidOwner: "user1", shareWith: "admin"),
|
id: "2", file: user1Files[0], uidOwner: "user1", shareWith: "admin"),
|
||||||
util.buildShare(id: "3", file: files[0], shareWith: "user2"),
|
util.buildShare(id: "3", file: files[0], shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
|
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
|
@ -330,12 +331,12 @@ Future<void> _removeSharedAlbumResyncedFile() async {
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
util.buildShare(id: "1", file: files[0], shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
pref: Pref.scoped(PrefMemoryProvider()),
|
pref: Pref.scoped(PrefMemoryProvider()),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
import 'package:nc_photos/db/entity_converter.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/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/scan_dir_offline.dart';
|
import 'package:nc_photos/use_case/scan_dir_offline.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../test_util.dart' as util;
|
import '../test_util.dart' as util;
|
||||||
|
@ -32,11 +33,11 @@ Future<void> _root() async {
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -61,11 +62,11 @@ Future<void> _subDir() async {
|
||||||
..addJpeg("admin/test/test2.jpg"))
|
..addJpeg("admin/test/test2.jpg"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -89,11 +90,11 @@ Future<void> _unsupportedFile() async {
|
||||||
..addGenericFile("admin/test2.pdf", "application/pdf"))
|
..addGenericFile("admin/test2.pdf", "application/pdf"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -126,13 +127,13 @@ Future<void> _multiAccountRoot() async {
|
||||||
..addJpeg("user1/test/test2.jpg", ownerId: "user1"))
|
..addJpeg("user1/test/test2.jpg", ownerId: "user1"))
|
||||||
.build();
|
.build();
|
||||||
final c = DiContainer(
|
final c = DiContainer(
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
await c.sqliteDb.insertAccountOf(user1Account);
|
await c.sqliteDb.insertAccounts([user1Account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
await util.insertFiles(c.sqliteDb, user1Account, user1Files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
import 'package:drift/drift.dart' as sql;
|
||||||
import 'package:event_bus/event_bus.dart';
|
import 'package:event_bus/event_bus.dart';
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:nc_photos/db/entity_converter.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/favorite.dart';
|
import 'package:nc_photos/entity/favorite.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
|
||||||
import 'package:nc_photos/use_case/sync_favorite.dart';
|
import 'package:nc_photos/use_case/sync_favorite.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
import '../mock_type.dart';
|
import '../mock_type.dart';
|
||||||
|
@ -36,11 +37,11 @@ Future<void> _new() async {
|
||||||
const Favorite(fileId: 103),
|
const Favorite(fileId: 103),
|
||||||
const Favorite(fileId: 104),
|
const Favorite(fileId: 104),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -66,11 +67,11 @@ Future<void> _remove() async {
|
||||||
const Favorite(fileId: 103),
|
const Favorite(fileId: 103),
|
||||||
const Favorite(fileId: 104),
|
const Favorite(fileId: 104),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
await c.sqliteDb.transaction(() async {
|
await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await util.insertFiles(c.sqliteDb, account, files);
|
await util.insertFiles(c.sqliteDb, account, files);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ Future<void> _remove() async {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Set<int>> _listSqliteDbFavoriteFileIds(sql.SqliteDb db) async {
|
Future<Set<int>> _listSqliteDbFavoriteFileIds(compat.SqliteDb db) async {
|
||||||
final query = db.selectOnly(db.files).join([
|
final query = db.selectOnly(db.files).join([
|
||||||
sql.innerJoin(
|
sql.innerJoin(
|
||||||
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
db.accountFiles, db.accountFiles.file.equalsExp(db.files.rowId)),
|
||||||
|
|
|
@ -1,152 +1,152 @@
|
||||||
import 'package:drift/drift.dart' as sql;
|
// import 'package:drift/drift.dart' as sql;
|
||||||
import 'package:nc_photos/account.dart';
|
// import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
// import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/sqlite/database.dart' as sql;
|
// import 'package:np_db_sqlite/np_db_sqlite_compat.dart' as compat;
|
||||||
import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
// import 'package:nc_photos/entity/sqlite/type_converter.dart';
|
||||||
import 'package:nc_photos/entity/tag.dart';
|
// import 'package:nc_photos/entity/tag.dart';
|
||||||
import 'package:nc_photos/entity/tag/data_source.dart';
|
// import 'package:nc_photos/entity/tag/data_source.dart';
|
||||||
import 'package:nc_photos/use_case/sync_tag.dart';
|
// import 'package:nc_photos/use_case/sync_tag.dart';
|
||||||
import 'package:test/test.dart';
|
// import 'package:test/test.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
// import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../mock_type.dart';
|
// import '../mock_type.dart';
|
||||||
import '../test_util.dart' as util;
|
// import '../test_util.dart' as util;
|
||||||
|
|
||||||
void main() {
|
// void main() {
|
||||||
group("SyncTag", () {
|
// group("SyncTag", () {
|
||||||
test("new", _new);
|
// test("new", _new);
|
||||||
test("remove", _remove);
|
// test("remove", _remove);
|
||||||
test("update", _update);
|
// test("update", _update);
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Sync with remote where there are new tags
|
// /// Sync with remote where there are new tags
|
||||||
///
|
// ///
|
||||||
/// Remote: [tag0, tag1, tag2]
|
// /// Remote: [tag0, tag1, tag2]
|
||||||
/// Local: [tag0]
|
// /// Local: [tag0]
|
||||||
/// Expect: [tag0, tag1, tag2]
|
// /// Expect: [tag0, tag1, tag2]
|
||||||
Future<void> _new() async {
|
// Future<void> _new() async {
|
||||||
final account = util.buildAccount();
|
// final account = util.buildAccount();
|
||||||
final c = DiContainer.late();
|
// final c = DiContainer.late();
|
||||||
c.sqliteDb = util.buildTestDb();
|
// c.sqliteDb = util.buildTestDb();
|
||||||
addTearDown(() => c.sqliteDb.close());
|
// addTearDown(() => c.sqliteDb.close());
|
||||||
c.tagRepoRemote = MockTagMemoryRepo({
|
// c.tagRepoRemote = MockTagMemoryRepo({
|
||||||
account.url: [
|
// account.url: [
|
||||||
const Tag(id: 10, displayName: "tag0"),
|
// const Tag(id: 10, displayName: "tag0"),
|
||||||
const Tag(id: 11, displayName: "tag1"),
|
// const Tag(id: 11, displayName: "tag1"),
|
||||||
const Tag(id: 12, displayName: "tag2"),
|
// const Tag(id: 12, displayName: "tag2"),
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
// c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
||||||
await c.sqliteDb.transaction(() async {
|
// await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
// await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.batch((batch) {
|
// await c.sqliteDb.batch((batch) {
|
||||||
batch.insert(c.sqliteDb.tags,
|
// batch.insert(c.sqliteDb.tags,
|
||||||
sql.TagsCompanion.insert(server: 1, tagId: 10, displayName: "tag0"));
|
// sql.TagsCompanion.insert(server: 1, tagId: 10, displayName: "tag0"));
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
await SyncTag(c)(account);
|
// await SyncTag(c)(account);
|
||||||
expect(
|
// expect(
|
||||||
await _listSqliteDbTags(c.sqliteDb),
|
// await _listSqliteDbTags(c.sqliteDb),
|
||||||
{
|
// {
|
||||||
account.url: {
|
// account.url: {
|
||||||
const Tag(id: 10, displayName: "tag0"),
|
// const Tag(id: 10, displayName: "tag0"),
|
||||||
const Tag(id: 11, displayName: "tag1"),
|
// const Tag(id: 11, displayName: "tag1"),
|
||||||
const Tag(id: 12, displayName: "tag2"),
|
// const Tag(id: 12, displayName: "tag2"),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Sync with remote where there are removed tags
|
// /// Sync with remote where there are removed tags
|
||||||
///
|
// ///
|
||||||
/// Remote: [tag0]
|
// /// Remote: [tag0]
|
||||||
/// Local: [tag0, tag1, tag2]
|
// /// Local: [tag0, tag1, tag2]
|
||||||
/// Expect: [tag0]
|
// /// Expect: [tag0]
|
||||||
Future<void> _remove() async {
|
// Future<void> _remove() async {
|
||||||
final account = util.buildAccount();
|
// final account = util.buildAccount();
|
||||||
final c = DiContainer.late();
|
// final c = DiContainer.late();
|
||||||
c.sqliteDb = util.buildTestDb();
|
// c.sqliteDb = util.buildTestDb();
|
||||||
addTearDown(() => c.sqliteDb.close());
|
// addTearDown(() => c.sqliteDb.close());
|
||||||
c.tagRepoRemote = MockTagMemoryRepo({
|
// c.tagRepoRemote = MockTagMemoryRepo({
|
||||||
account.url: [
|
// account.url: [
|
||||||
const Tag(id: 10, displayName: "tag0"),
|
// const Tag(id: 10, displayName: "tag0"),
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
// c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
||||||
await c.sqliteDb.transaction(() async {
|
// await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
// await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.batch((batch) {
|
// await c.sqliteDb.batch((batch) {
|
||||||
batch.insertAll(c.sqliteDb.tags, [
|
// batch.insertAll(c.sqliteDb.tags, [
|
||||||
sql.TagsCompanion.insert(server: 1, tagId: 10, displayName: "tag0"),
|
// sql.TagsCompanion.insert(server: 1, tagId: 10, displayName: "tag0"),
|
||||||
sql.TagsCompanion.insert(server: 1, tagId: 11, displayName: "tag1"),
|
// sql.TagsCompanion.insert(server: 1, tagId: 11, displayName: "tag1"),
|
||||||
sql.TagsCompanion.insert(server: 1, tagId: 12, displayName: "tag2"),
|
// sql.TagsCompanion.insert(server: 1, tagId: 12, displayName: "tag2"),
|
||||||
]);
|
// ]);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
await SyncTag(c)(account);
|
// await SyncTag(c)(account);
|
||||||
expect(
|
// expect(
|
||||||
await _listSqliteDbTags(c.sqliteDb),
|
// await _listSqliteDbTags(c.sqliteDb),
|
||||||
{
|
// {
|
||||||
account.url: {
|
// account.url: {
|
||||||
const Tag(id: 10, displayName: "tag0"),
|
// const Tag(id: 10, displayName: "tag0"),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
/// Sync with remote where there are updated tags (i.e, same id, different
|
// /// Sync with remote where there are updated tags (i.e, same id, different
|
||||||
/// properties)
|
// /// properties)
|
||||||
///
|
// ///
|
||||||
/// Remote: [tag0, new tag1]
|
// /// Remote: [tag0, new tag1]
|
||||||
/// Local: [tag0, tag1]
|
// /// Local: [tag0, tag1]
|
||||||
/// Expect: [tag0, new tag1]
|
// /// Expect: [tag0, new tag1]
|
||||||
Future<void> _update() async {
|
// Future<void> _update() async {
|
||||||
final account = util.buildAccount();
|
// final account = util.buildAccount();
|
||||||
final c = DiContainer.late();
|
// final c = DiContainer.late();
|
||||||
c.sqliteDb = util.buildTestDb();
|
// c.sqliteDb = util.buildTestDb();
|
||||||
addTearDown(() => c.sqliteDb.close());
|
// addTearDown(() => c.sqliteDb.close());
|
||||||
c.tagRepoRemote = MockTagMemoryRepo({
|
// c.tagRepoRemote = MockTagMemoryRepo({
|
||||||
account.url: [
|
// account.url: [
|
||||||
const Tag(id: 10, displayName: "tag0"),
|
// const Tag(id: 10, displayName: "tag0"),
|
||||||
const Tag(id: 11, displayName: "new tag1"),
|
// const Tag(id: 11, displayName: "new tag1"),
|
||||||
],
|
// ],
|
||||||
});
|
// });
|
||||||
c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
// c.tagRepoLocal = TagRepo(TagSqliteDbDataSource(c.sqliteDb));
|
||||||
await c.sqliteDb.transaction(() async {
|
// await c.sqliteDb.transaction(() async {
|
||||||
await c.sqliteDb.insertAccountOf(account);
|
// await c.sqliteDb.insertAccounts([account.toDb()]);
|
||||||
await c.sqliteDb.batch((batch) {
|
// await c.sqliteDb.batch((batch) {
|
||||||
batch.insertAll(c.sqliteDb.tags, [
|
// batch.insertAll(c.sqliteDb.tags, [
|
||||||
sql.TagsCompanion.insert(server: 1, tagId: 10, displayName: "tag0"),
|
// sql.TagsCompanion.insert(server: 1, tagId: 10, displayName: "tag0"),
|
||||||
sql.TagsCompanion.insert(server: 1, tagId: 11, displayName: "tag1"),
|
// sql.TagsCompanion.insert(server: 1, tagId: 11, displayName: "tag1"),
|
||||||
]);
|
// ]);
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
await SyncTag(c)(account);
|
// await SyncTag(c)(account);
|
||||||
expect(
|
// expect(
|
||||||
await _listSqliteDbTags(c.sqliteDb),
|
// await _listSqliteDbTags(c.sqliteDb),
|
||||||
{
|
// {
|
||||||
account.url: {
|
// account.url: {
|
||||||
const Tag(id: 10, displayName: "tag0"),
|
// const Tag(id: 10, displayName: "tag0"),
|
||||||
const Tag(id: 11, displayName: "new tag1"),
|
// const Tag(id: 11, displayName: "new tag1"),
|
||||||
},
|
// },
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
Future<Map<String, Set<Tag>>> _listSqliteDbTags(sql.SqliteDb db) async {
|
// Future<Map<String, Set<Tag>>> _listSqliteDbTags(sql.SqliteDb db) async {
|
||||||
final query = db.select(db.tags).join([
|
// final query = db.select(db.tags).join([
|
||||||
sql.innerJoin(db.servers, db.servers.rowId.equalsExp(db.tags.server)),
|
// sql.innerJoin(db.servers, db.servers.rowId.equalsExp(db.tags.server)),
|
||||||
]);
|
// ]);
|
||||||
final result = await query
|
// final result = await query
|
||||||
.map((r) => Tuple2(r.readTable(db.servers), r.readTable(db.tags)))
|
// .map((r) => Tuple2(r.readTable(db.servers), r.readTable(db.tags)))
|
||||||
.get();
|
// .get();
|
||||||
final product = <String, Set<Tag>>{};
|
// final product = <String, Set<Tag>>{};
|
||||||
for (final r in result) {
|
// for (final r in result) {
|
||||||
(product[r.item1.address] ??= {}).add(SqliteTagConverter.fromSql(r.item2));
|
// (product[r.item1.address] ??= {}).add(SqliteTagConverter.fromSql(r.item2));
|
||||||
}
|
// }
|
||||||
return product;
|
// return product;
|
||||||
}
|
// }
|
||||||
|
|
|
@ -36,7 +36,7 @@ Future<void> _unshareWithoutFile() async {
|
||||||
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
util.buildShare(id: "0", file: albumFile, shareWith: "user1"),
|
||||||
util.buildShare(id: "1", file: albumFile, shareWith: "user2"),
|
util.buildShare(id: "1", file: albumFile, shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ Future<void> _unshareWithFile() async {
|
||||||
util.buildShare(id: "2", file: file1, shareWith: "user1"),
|
util.buildShare(id: "2", file: file1, shareWith: "user1"),
|
||||||
util.buildShare(id: "3", file: file1, shareWith: "user2"),
|
util.buildShare(id: "3", file: file1, shareWith: "user2"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ Future<void> _unshareWithFileNotOwned() async {
|
||||||
util.buildShare(
|
util.buildShare(
|
||||||
id: "5", uidOwner: "user2", file: files[1], shareWith: "user1"),
|
id: "5", uidOwner: "user2", file: files[1], shareWith: "user1"),
|
||||||
]),
|
]),
|
||||||
sqliteDb: util.buildTestDb(),
|
npDb: util.buildTestDb(),
|
||||||
);
|
);
|
||||||
addTearDown(() => c.sqliteDb.close());
|
addTearDown(() => c.sqliteDb.close());
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
extension MapEntryListExtension<T, U> on Iterable<MapEntry<T, U>> {
|
extension MapEntryListExtension<T, U> on Iterable<MapEntry<T, U>> {
|
||||||
Map<T, U> toMap() => Map.fromEntries(this);
|
Map<T, U> toMap() => Map.fromEntries(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension MapExtension<T, U> on Map<T, U> {
|
||||||
|
Future<Map<V, W>> asyncMap<V, W>(
|
||||||
|
FutureOr<MapEntry<V, W>> Function(T key, U value) convert) async {
|
||||||
|
final results = await Future.wait(
|
||||||
|
entries.map((e) async => await convert(e.key, e.value)));
|
||||||
|
return Map.fromEntries(results);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
7
np_datetime/.gitignore
vendored
Normal file
7
np_datetime/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# https://dart.dev/guides/libraries/private-files
|
||||||
|
# Created by `dart pub`
|
||||||
|
.dart_tool/
|
||||||
|
|
||||||
|
# Avoid committing pubspec.lock for library packages; see
|
||||||
|
# https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
pubspec.lock
|
1
np_datetime/analysis_options.yaml
Normal file
1
np_datetime/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: package:np_lints/np.yaml
|
3
np_datetime/lib/np_datetime.dart
Normal file
3
np_datetime/lib/np_datetime.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
library np_datetime;
|
||||||
|
|
||||||
|
export 'src/time_range.dart';
|
51
np_datetime/lib/src/time_range.dart
Normal file
51
np_datetime/lib/src/time_range.dart
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
enum TimeRangeBound {
|
||||||
|
inclusive,
|
||||||
|
exclusive,
|
||||||
|
}
|
||||||
|
|
||||||
|
class TimeRange {
|
||||||
|
const TimeRange({
|
||||||
|
required this.from,
|
||||||
|
this.fromBound = TimeRangeBound.inclusive,
|
||||||
|
required this.to,
|
||||||
|
this.toBound = TimeRangeBound.exclusive,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return "${fromBound == TimeRangeBound.inclusive ? "[" : "("}"
|
||||||
|
"$from, $to"
|
||||||
|
"${toBound == TimeRangeBound.inclusive ? "]" : ")"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
final DateTime from;
|
||||||
|
final TimeRangeBound fromBound;
|
||||||
|
final DateTime to;
|
||||||
|
final TimeRangeBound toBound;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TimeRangeExtension on TimeRange {
|
||||||
|
/// Return if an arbitrary time [a] is inside this range
|
||||||
|
///
|
||||||
|
/// The comparison is independent of whether the time is in UTC or in the
|
||||||
|
/// local time zone
|
||||||
|
bool isIn(DateTime a) {
|
||||||
|
if (a.isBefore(from)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (fromBound == TimeRangeBound.exclusive) {
|
||||||
|
if (a.isAtSameMomentAs(from)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (a.isAfter(to)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (toBound == TimeRangeBound.exclusive) {
|
||||||
|
if (a.isAtSameMomentAs(to)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
13
np_datetime/pubspec.yaml
Normal file
13
np_datetime/pubspec.yaml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
name: np_datetime
|
||||||
|
description: A starting point for Dart libraries or applications.
|
||||||
|
version: 1.0.0
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
publish_to: none
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.19.6 <3.0.0'
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
np_lints:
|
||||||
|
path: ../np_lints
|
||||||
|
test: ^1.21.0
|
32
np_db/.gitignore
vendored
Normal file
32
np_db/.gitignore
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
1
np_db/analysis_options.yaml
Normal file
1
np_db/analysis_options.yaml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
include: package:np_lints/np.yaml
|
11
np_db/build.yaml
Normal file
11
np_db/build.yaml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
targets:
|
||||||
|
$default:
|
||||||
|
builders:
|
||||||
|
to_string_build:
|
||||||
|
options:
|
||||||
|
formatStringNameMapping:
|
||||||
|
double: "${$?.toStringAsFixed(3)}"
|
||||||
|
List: "[length: ${$?.length}]"
|
||||||
|
File: "${$?.path}"
|
||||||
|
FileDescriptor: "${$?.fdPath}"
|
||||||
|
useEnumName: true
|
5
np_db/lib/np_db.dart
Normal file
5
np_db/lib/np_db.dart
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
library np_db;
|
||||||
|
|
||||||
|
export 'src/api.dart';
|
||||||
|
export 'src/entity.dart';
|
||||||
|
export 'src/exception.dart';
|
389
np_db/lib/src/api.dart
Normal file
389
np_db/lib/src/api.dart
Normal file
|
@ -0,0 +1,389 @@
|
||||||
|
import 'dart:io' as io;
|
||||||
|
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:np_common/or_null.dart';
|
||||||
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_datetime/np_datetime.dart';
|
||||||
|
import 'package:np_db/src/entity.dart';
|
||||||
|
import 'package:np_db_sqlite/np_db_sqlite.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'api.g.dart';
|
||||||
|
|
||||||
|
typedef NpDbComputeCallback<T, U> = Future<U> Function(NpDb db, T message);
|
||||||
|
|
||||||
|
/// A data structure that identify a File in db
|
||||||
|
@ToString(ignoreNull: true)
|
||||||
|
class DbFileKey {
|
||||||
|
const DbFileKey({
|
||||||
|
this.fileId,
|
||||||
|
this.relativePath,
|
||||||
|
}) : assert(fileId != null || relativePath != null);
|
||||||
|
|
||||||
|
const DbFileKey.byId(int fileId) : this(fileId: fileId);
|
||||||
|
|
||||||
|
const DbFileKey.byPath(String relativePath)
|
||||||
|
: this(relativePath: relativePath);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
bool compareIdentity(DbFileKey other) =>
|
||||||
|
fileId == other.fileId || relativePath == other.relativePath;
|
||||||
|
|
||||||
|
final int? fileId;
|
||||||
|
final String? relativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
class DbSyncResult {
|
||||||
|
const DbSyncResult({
|
||||||
|
required this.insert,
|
||||||
|
required this.delete,
|
||||||
|
required this.update,
|
||||||
|
});
|
||||||
|
|
||||||
|
final int insert;
|
||||||
|
final int delete;
|
||||||
|
final int update;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class DbLocationGroup with EquatableMixin {
|
||||||
|
const DbLocationGroup({
|
||||||
|
required this.place,
|
||||||
|
required this.countryCode,
|
||||||
|
required this.count,
|
||||||
|
required this.latestFileId,
|
||||||
|
required this.latestDateTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
place,
|
||||||
|
countryCode,
|
||||||
|
count,
|
||||||
|
latestFileId,
|
||||||
|
latestDateTime,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String place;
|
||||||
|
final String countryCode;
|
||||||
|
final int count;
|
||||||
|
final int latestFileId;
|
||||||
|
final DateTime latestDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class DbLocationGroupResult {
|
||||||
|
const DbLocationGroupResult({
|
||||||
|
required this.name,
|
||||||
|
required this.admin1,
|
||||||
|
required this.admin2,
|
||||||
|
required this.countryCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final List<DbLocationGroup> name;
|
||||||
|
final List<DbLocationGroup> admin1;
|
||||||
|
final List<DbLocationGroup> admin2;
|
||||||
|
final List<DbLocationGroup> countryCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
abstract class NpDb {
|
||||||
|
factory NpDb() => NpDbSqlite();
|
||||||
|
|
||||||
|
/// Initialize the db for the main isolate
|
||||||
|
///
|
||||||
|
/// If running on android, you must pass the current SDK int to [androidSdk].
|
||||||
|
/// If running on other platforms, this value will be ignored, you can pass
|
||||||
|
/// null in such case
|
||||||
|
Future<void> initMainIsolate({
|
||||||
|
required int? androidSdk,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Initialize the db for a background isolate
|
||||||
|
///
|
||||||
|
/// If running on android, you must pass the current SDK int to [androidSdk].
|
||||||
|
/// If running on other platforms, this value will be ignored, you can pass
|
||||||
|
/// null in such case
|
||||||
|
Future<void> initBackgroundIsolate({
|
||||||
|
required int? androidSdk,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Dispose the db
|
||||||
|
///
|
||||||
|
/// After disposing, you must not call any methods defined here anymore. This
|
||||||
|
/// is typically used before stopping a background isolate
|
||||||
|
Future<void> dispose();
|
||||||
|
|
||||||
|
Future<io.File> export(io.Directory dir);
|
||||||
|
|
||||||
|
/// Start an isolate with a [NpDb] instance provided to you
|
||||||
|
Future<U> compute<T, U>(NpDbComputeCallback<T, U> callback, T args);
|
||||||
|
|
||||||
|
/// Insert [accounts] to db
|
||||||
|
Future<void> addAccounts(List<DbAccount> accounts);
|
||||||
|
|
||||||
|
/// Clear all data in the database and insert [accounts]
|
||||||
|
///
|
||||||
|
/// WARNING: ALL data will be dropped!
|
||||||
|
Future<void> clearAndInitWithAccounts(List<DbAccount> accounts);
|
||||||
|
|
||||||
|
Future<void> deleteAccount(DbAccount account);
|
||||||
|
|
||||||
|
Future<List<DbAlbum>> getAlbumsByAlbumFileIds({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> fileIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> syncAlbum({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbFile albumFile,
|
||||||
|
required DbAlbum album,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return all faces provided by the Face Recognition app
|
||||||
|
Future<List<DbFaceRecognitionPerson>> getFaceRecognitionPersons({
|
||||||
|
required DbAccount account,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return faces provided by the Face Recognition app with loosely matched
|
||||||
|
/// [name]
|
||||||
|
Future<List<DbFaceRecognitionPerson>> searchFaceRecognitionPersonsByName({
|
||||||
|
required DbAccount account,
|
||||||
|
required String name,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Replace all recognized people for [account]
|
||||||
|
Future<DbSyncResult> syncFaceRecognitionPersons({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<DbFaceRecognitionPerson> persons,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return files located inside [dir]
|
||||||
|
Future<List<DbFile>> getFilesByDirKey({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbFileKey dir,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<DbFile>> getFilesByDirKeyAndLocation({
|
||||||
|
required DbAccount account,
|
||||||
|
required String dirRelativePath,
|
||||||
|
required String? place,
|
||||||
|
required String countryCode,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return [DbFile]s by their corresponding file ids
|
||||||
|
///
|
||||||
|
/// No error will be thrown even if a file in [fileIds] is not found, it is
|
||||||
|
/// thus the responsibility of the caller to decide how to handle such case.
|
||||||
|
/// Returned files are NOT guaranteed to be sorted as [fileIds]
|
||||||
|
Future<List<DbFile>> getFilesByFileIds({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> fileIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return [DbFile]s by their date time value
|
||||||
|
Future<List<DbFile>> getFilesByTimeRange({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<String> dirRoots,
|
||||||
|
required TimeRange range,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Update one or more file properties of a single file
|
||||||
|
Future<void> updateFileByFileId({
|
||||||
|
required DbAccount account,
|
||||||
|
required int fileId,
|
||||||
|
String? relativePath,
|
||||||
|
OrNull<bool>? isFavorite,
|
||||||
|
OrNull<bool>? isArchived,
|
||||||
|
OrNull<DateTime>? overrideDateTime,
|
||||||
|
DateTime? bestDateTime,
|
||||||
|
OrNull<DbImageData>? imageData,
|
||||||
|
OrNull<DbLocation>? location,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Batch update one or more file properties of multiple files
|
||||||
|
///
|
||||||
|
/// Only a subset of properties can be updated in batch
|
||||||
|
Future<void> updateFilesByFileIds({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> fileIds,
|
||||||
|
OrNull<bool>? isFavorite,
|
||||||
|
OrNull<bool>? isArchived,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Add or replace files in db
|
||||||
|
Future<void> syncDirFiles({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbFileKey dirFile,
|
||||||
|
required List<DbFile> files,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Replace a file in db
|
||||||
|
Future<void> syncFile({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbFile file,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Add or replace nc albums in db
|
||||||
|
Future<DbSyncResult> syncFavoriteFiles({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> favoriteFileIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return number of files without metadata
|
||||||
|
Future<int> countFilesByFileIdsMissingMetadata({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<int> fileIds,
|
||||||
|
required List<String> mimes,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Delete a file or dir from db
|
||||||
|
Future<void> deleteFile({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbFileKey file,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return a map of file id to etags for all dirs and sub dirs located under
|
||||||
|
/// [relativePath], including the path itself
|
||||||
|
Future<Map<int, String>> getDirFileIdToEtagByLikeRelativePath({
|
||||||
|
required DbAccount account,
|
||||||
|
required String relativePath,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Remove all children of a dir
|
||||||
|
Future<void> truncateDir({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbFileKey dir,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return [DbFileDescriptor]s
|
||||||
|
///
|
||||||
|
/// Limit results by their corresponding file ids if [fileIds] is not null. No
|
||||||
|
/// error will be thrown even if a file in [fileIds] is not found, it is thus
|
||||||
|
/// the responsibility of the caller to decide how to handle such case
|
||||||
|
///
|
||||||
|
/// [includeRelativeRoots] define paths to be included; [excludeRelativeRoots]
|
||||||
|
/// define paths to be excluded. Paths in both lists are matched as prefix
|
||||||
|
///
|
||||||
|
/// Limit type of files to be returned by specifying [mimes]. The mime types
|
||||||
|
/// are matched as is
|
||||||
|
///
|
||||||
|
/// Returned files are sorted by [DbFileDescriptor.bestDateTime] in descending
|
||||||
|
/// order
|
||||||
|
Future<List<DbFileDescriptor>> getFileDescriptors({
|
||||||
|
required DbAccount account,
|
||||||
|
List<int>? fileIds,
|
||||||
|
List<String>? includeRelativeRoots,
|
||||||
|
List<String>? excludeRelativeRoots,
|
||||||
|
List<String>? relativePathKeywords,
|
||||||
|
String? location,
|
||||||
|
bool? isFavorite,
|
||||||
|
List<String>? mimes,
|
||||||
|
int? limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<DbLocationGroupResult> groupLocations({
|
||||||
|
required DbAccount account,
|
||||||
|
List<String>? includeRelativeRoots,
|
||||||
|
List<String>? excludeRelativeRoots,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<DbNcAlbum>> getNcAlbums({
|
||||||
|
required DbAccount account,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> addNcAlbum({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbNcAlbum album,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<void> deleteNcAlbum({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbNcAlbum album,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Add or replace nc albums in db
|
||||||
|
Future<DbSyncResult> syncNcAlbums({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<DbNcAlbum> albums,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<DbNcAlbumItem>> getNcAlbumItemsByParent({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbNcAlbum parent,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Add or replace nc album items in db
|
||||||
|
Future<DbSyncResult> syncNcAlbumItems({
|
||||||
|
required DbAccount account,
|
||||||
|
required DbNcAlbum album,
|
||||||
|
required List<DbNcAlbumItem> items,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return all faces provided by the Recognize app
|
||||||
|
Future<List<DbRecognizeFace>> getRecognizeFaces({
|
||||||
|
required DbAccount account,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<List<DbRecognizeFaceItem>> getRecognizeFaceItemsByFaceLabel({
|
||||||
|
required DbAccount account,
|
||||||
|
required String label,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Map<String, List<DbRecognizeFaceItem>>>
|
||||||
|
getRecognizeFaceItemsByFaceLabels({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<String> labels,
|
||||||
|
ErrorWithValueHandler<String>? onError,
|
||||||
|
});
|
||||||
|
|
||||||
|
Future<Map<String, DbRecognizeFaceItem>>
|
||||||
|
getLatestRecognizeFaceItemsByFaceLabels({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<String> labels,
|
||||||
|
ErrorWithValueHandler<String>? onError,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Replace all recognized faces for [account]
|
||||||
|
///
|
||||||
|
/// Return true if any of the faces or items are changed
|
||||||
|
Future<bool> syncRecognizeFacesAndItems({
|
||||||
|
required DbAccount account,
|
||||||
|
required Map<DbRecognizeFace, List<DbRecognizeFaceItem>> data,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return all tags
|
||||||
|
Future<List<DbTag>> getTags({
|
||||||
|
required DbAccount account,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Return the tag matching [displayName]
|
||||||
|
Future<DbTag?> getTagByDisplayName({
|
||||||
|
required DbAccount account,
|
||||||
|
required String displayName,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Replace all tags for [account]
|
||||||
|
Future<DbSyncResult> syncTags({
|
||||||
|
required DbAccount account,
|
||||||
|
required List<DbTag> tags,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Migrate to app v55
|
||||||
|
Future<void> migrateV55(void Function(int current, int count)? onProgress);
|
||||||
|
|
||||||
|
/// Run vacuum statement on a database backed by sqlite
|
||||||
|
///
|
||||||
|
/// This method is not necessarily supported by all implementations
|
||||||
|
Future<void> sqlVacuum();
|
||||||
|
}
|
39
np_db/lib/src/api.g.dart
Normal file
39
np_db/lib/src/api.g.dart
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'api.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$NpDbNpLog on NpDb {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger("src.api.NpDb");
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$DbFileKeyToString on DbFileKey {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbFileKey {${fileId == null ? "" : "fileId: $fileId, "}${relativePath == null ? "" : "relativePath: $relativePath"}}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbLocationGroupToString on DbLocationGroup {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbLocationGroup {place: $place, countryCode: $countryCode, count: $count, latestFileId: $latestFileId, latestDateTime: $latestDateTime}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbLocationGroupResultToString on DbLocationGroupResult {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbLocationGroupResult {name: [length: ${name.length}], admin1: [length: ${admin1.length}], admin2: [length: ${admin2.length}], countryCode: [length: ${countryCode.length}]}";
|
||||||
|
}
|
||||||
|
}
|
496
np_db/lib/src/entity.dart
Normal file
496
np_db/lib/src/entity.dart
Normal file
|
@ -0,0 +1,496 @@
|
||||||
|
import 'package:copy_with/copy_with.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:np_common/type.dart';
|
||||||
|
import 'package:np_string/np_string.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'entity.g.dart';
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbAccount with EquatableMixin {
|
||||||
|
const DbAccount({
|
||||||
|
required this.serverAddress,
|
||||||
|
required this.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
serverAddress,
|
||||||
|
userId,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String serverAddress;
|
||||||
|
final CiString userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbAlbum with EquatableMixin {
|
||||||
|
const DbAlbum({
|
||||||
|
required this.fileId,
|
||||||
|
this.fileEtag,
|
||||||
|
required this.version,
|
||||||
|
required this.lastUpdated,
|
||||||
|
required this.name,
|
||||||
|
required this.providerType,
|
||||||
|
required this.providerContent,
|
||||||
|
required this.coverProviderType,
|
||||||
|
required this.coverProviderContent,
|
||||||
|
required this.sortProviderType,
|
||||||
|
required this.sortProviderContent,
|
||||||
|
required this.shares,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
fileId,
|
||||||
|
fileEtag,
|
||||||
|
version,
|
||||||
|
lastUpdated,
|
||||||
|
name,
|
||||||
|
providerType,
|
||||||
|
providerContent,
|
||||||
|
coverProviderType,
|
||||||
|
coverProviderContent,
|
||||||
|
sortProviderType,
|
||||||
|
sortProviderContent,
|
||||||
|
shares,
|
||||||
|
];
|
||||||
|
|
||||||
|
final int fileId;
|
||||||
|
final String? fileEtag;
|
||||||
|
final int version;
|
||||||
|
final DateTime lastUpdated;
|
||||||
|
final String name;
|
||||||
|
final String providerType;
|
||||||
|
final JsonObj providerContent;
|
||||||
|
final String coverProviderType;
|
||||||
|
final JsonObj coverProviderContent;
|
||||||
|
final String sortProviderType;
|
||||||
|
final JsonObj sortProviderContent;
|
||||||
|
|
||||||
|
final List<DbAlbumShare> shares;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbAlbumShare with EquatableMixin {
|
||||||
|
const DbAlbumShare({
|
||||||
|
required this.userId,
|
||||||
|
this.displayName,
|
||||||
|
required this.sharedAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
userId,
|
||||||
|
displayName,
|
||||||
|
sharedAt,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String userId;
|
||||||
|
final String? displayName;
|
||||||
|
final DateTime sharedAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbFaceRecognitionPerson with EquatableMixin {
|
||||||
|
const DbFaceRecognitionPerson({
|
||||||
|
required this.name,
|
||||||
|
required this.thumbFaceId,
|
||||||
|
required this.count,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
name,
|
||||||
|
thumbFaceId,
|
||||||
|
count,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String name;
|
||||||
|
final int thumbFaceId;
|
||||||
|
final int count;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbFile with EquatableMixin {
|
||||||
|
const DbFile({
|
||||||
|
required this.fileId,
|
||||||
|
required this.contentLength,
|
||||||
|
required this.contentType,
|
||||||
|
required this.etag,
|
||||||
|
required this.lastModified,
|
||||||
|
required this.isCollection,
|
||||||
|
required this.usedBytes,
|
||||||
|
required this.hasPreview,
|
||||||
|
required this.ownerId,
|
||||||
|
required this.ownerDisplayName,
|
||||||
|
required this.relativePath,
|
||||||
|
required this.isFavorite,
|
||||||
|
required this.isArchived,
|
||||||
|
required this.overrideDateTime,
|
||||||
|
required this.bestDateTime,
|
||||||
|
required this.imageData,
|
||||||
|
required this.location,
|
||||||
|
required this.trashData,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
fileId,
|
||||||
|
contentLength,
|
||||||
|
contentType,
|
||||||
|
etag,
|
||||||
|
lastModified,
|
||||||
|
isCollection,
|
||||||
|
usedBytes,
|
||||||
|
hasPreview,
|
||||||
|
ownerId,
|
||||||
|
ownerDisplayName,
|
||||||
|
relativePath,
|
||||||
|
isFavorite,
|
||||||
|
isArchived,
|
||||||
|
overrideDateTime,
|
||||||
|
bestDateTime,
|
||||||
|
imageData,
|
||||||
|
location,
|
||||||
|
trashData,
|
||||||
|
];
|
||||||
|
|
||||||
|
final int fileId;
|
||||||
|
final int? contentLength;
|
||||||
|
final String? contentType;
|
||||||
|
final String? etag;
|
||||||
|
final DateTime? lastModified;
|
||||||
|
final bool? isCollection;
|
||||||
|
final int? usedBytes;
|
||||||
|
final bool? hasPreview;
|
||||||
|
final CiString? ownerId;
|
||||||
|
final String? ownerDisplayName;
|
||||||
|
final String relativePath;
|
||||||
|
final bool? isFavorite;
|
||||||
|
final bool? isArchived;
|
||||||
|
final DateTime? overrideDateTime;
|
||||||
|
final DateTime bestDateTime;
|
||||||
|
final DbImageData? imageData;
|
||||||
|
final DbLocation? location;
|
||||||
|
final DbTrashData? trashData;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbFileDescriptor with EquatableMixin {
|
||||||
|
const DbFileDescriptor({
|
||||||
|
required this.relativePath,
|
||||||
|
required this.fileId,
|
||||||
|
required this.contentType,
|
||||||
|
required this.isArchived,
|
||||||
|
required this.isFavorite,
|
||||||
|
required this.bestDateTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
relativePath,
|
||||||
|
fileId,
|
||||||
|
contentType,
|
||||||
|
isArchived,
|
||||||
|
isFavorite,
|
||||||
|
bestDateTime,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String relativePath;
|
||||||
|
final int fileId;
|
||||||
|
final String? contentType;
|
||||||
|
final bool? isArchived;
|
||||||
|
final bool? isFavorite;
|
||||||
|
final DateTime bestDateTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbImageData with EquatableMixin {
|
||||||
|
const DbImageData({
|
||||||
|
required this.lastUpdated,
|
||||||
|
required this.fileEtag,
|
||||||
|
required this.width,
|
||||||
|
required this.height,
|
||||||
|
required this.exif,
|
||||||
|
required this.exifDateTimeOriginal,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
lastUpdated,
|
||||||
|
fileEtag,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
exif,
|
||||||
|
exifDateTimeOriginal,
|
||||||
|
];
|
||||||
|
|
||||||
|
final DateTime lastUpdated;
|
||||||
|
final String? fileEtag;
|
||||||
|
final int? width;
|
||||||
|
final int? height;
|
||||||
|
final JsonObj? exif;
|
||||||
|
final DateTime? exifDateTimeOriginal;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbLocation with EquatableMixin {
|
||||||
|
const DbLocation({
|
||||||
|
required this.version,
|
||||||
|
required this.name,
|
||||||
|
required this.latitude,
|
||||||
|
required this.longitude,
|
||||||
|
required this.countryCode,
|
||||||
|
required this.admin1,
|
||||||
|
required this.admin2,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
version,
|
||||||
|
name,
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
countryCode,
|
||||||
|
admin1,
|
||||||
|
admin2,
|
||||||
|
];
|
||||||
|
|
||||||
|
final int version;
|
||||||
|
final String? name;
|
||||||
|
final double? latitude;
|
||||||
|
final double? longitude;
|
||||||
|
final String? countryCode;
|
||||||
|
final String? admin1;
|
||||||
|
final String? admin2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbNcAlbum with EquatableMixin {
|
||||||
|
const DbNcAlbum({
|
||||||
|
required this.relativePath,
|
||||||
|
this.lastPhoto,
|
||||||
|
required this.nbItems,
|
||||||
|
this.location,
|
||||||
|
this.dateStart,
|
||||||
|
this.dateEnd,
|
||||||
|
required this.collaborators,
|
||||||
|
required this.isOwned,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
relativePath,
|
||||||
|
lastPhoto,
|
||||||
|
nbItems,
|
||||||
|
location,
|
||||||
|
dateStart,
|
||||||
|
dateEnd,
|
||||||
|
collaborators,
|
||||||
|
isOwned,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String relativePath;
|
||||||
|
final int? lastPhoto;
|
||||||
|
final int nbItems;
|
||||||
|
final String? location;
|
||||||
|
final DateTime? dateStart;
|
||||||
|
final DateTime? dateEnd;
|
||||||
|
final List<JsonObj> collaborators;
|
||||||
|
final bool isOwned;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbNcAlbumItem with EquatableMixin {
|
||||||
|
const DbNcAlbumItem({
|
||||||
|
required this.relativePath,
|
||||||
|
required this.fileId,
|
||||||
|
this.contentLength,
|
||||||
|
this.contentType,
|
||||||
|
this.etag,
|
||||||
|
this.lastModified,
|
||||||
|
this.hasPreview,
|
||||||
|
this.isFavorite,
|
||||||
|
this.fileMetadataWidth,
|
||||||
|
this.fileMetadataHeight,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
relativePath,
|
||||||
|
fileId,
|
||||||
|
contentLength,
|
||||||
|
contentType,
|
||||||
|
etag,
|
||||||
|
lastModified,
|
||||||
|
hasPreview,
|
||||||
|
isFavorite,
|
||||||
|
fileMetadataWidth,
|
||||||
|
fileMetadataHeight,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String relativePath;
|
||||||
|
final int fileId;
|
||||||
|
final int? contentLength;
|
||||||
|
final String? contentType;
|
||||||
|
final String? etag;
|
||||||
|
final DateTime? lastModified;
|
||||||
|
final bool? hasPreview;
|
||||||
|
final bool? isFavorite;
|
||||||
|
final int? fileMetadataWidth;
|
||||||
|
final int? fileMetadataHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbRecognizeFace with EquatableMixin {
|
||||||
|
const DbRecognizeFace({
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
label,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbRecognizeFaceItem with EquatableMixin {
|
||||||
|
const DbRecognizeFaceItem({
|
||||||
|
required this.relativePath,
|
||||||
|
required this.fileId,
|
||||||
|
this.contentLength,
|
||||||
|
this.contentType,
|
||||||
|
this.etag,
|
||||||
|
this.lastModified,
|
||||||
|
this.hasPreview,
|
||||||
|
this.realPath,
|
||||||
|
this.isFavorite,
|
||||||
|
this.fileMetadataWidth,
|
||||||
|
this.fileMetadataHeight,
|
||||||
|
this.faceDetections,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
relativePath,
|
||||||
|
fileId,
|
||||||
|
contentLength,
|
||||||
|
contentType,
|
||||||
|
etag,
|
||||||
|
lastModified,
|
||||||
|
hasPreview,
|
||||||
|
realPath,
|
||||||
|
isFavorite,
|
||||||
|
fileMetadataWidth,
|
||||||
|
fileMetadataHeight,
|
||||||
|
faceDetections,
|
||||||
|
];
|
||||||
|
|
||||||
|
final String relativePath;
|
||||||
|
final int fileId;
|
||||||
|
final int? contentLength;
|
||||||
|
final String? contentType;
|
||||||
|
final String? etag;
|
||||||
|
final DateTime? lastModified;
|
||||||
|
final bool? hasPreview;
|
||||||
|
final String? realPath;
|
||||||
|
final bool? isFavorite;
|
||||||
|
final int? fileMetadataWidth;
|
||||||
|
final int? fileMetadataHeight;
|
||||||
|
final String? faceDetections;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbTag with EquatableMixin {
|
||||||
|
const DbTag({
|
||||||
|
required this.id,
|
||||||
|
required this.displayName,
|
||||||
|
required this.userVisible,
|
||||||
|
required this.userAssignable,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props => [
|
||||||
|
id,
|
||||||
|
displayName,
|
||||||
|
userVisible,
|
||||||
|
userAssignable,
|
||||||
|
];
|
||||||
|
|
||||||
|
final int id;
|
||||||
|
final String displayName;
|
||||||
|
final bool? userVisible;
|
||||||
|
final bool? userAssignable;
|
||||||
|
}
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class DbTrashData {
|
||||||
|
const DbTrashData({
|
||||||
|
required this.filename,
|
||||||
|
required this.originalLocation,
|
||||||
|
required this.deletionTime,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final String filename;
|
||||||
|
final String originalLocation;
|
||||||
|
final DateTime deletionTime;
|
||||||
|
}
|
744
np_db/lib/src/entity.g.dart
Normal file
744
np_db/lib/src/entity.g.dart
Normal file
|
@ -0,0 +1,744 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'entity.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithLintRuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: library_private_types_in_public_api, duplicate_ignore
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $DbAccountCopyWithWorker {
|
||||||
|
DbAccount call({String? serverAddress, CiString? userId});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbAccountCopyWithWorkerImpl implements $DbAccountCopyWithWorker {
|
||||||
|
_$DbAccountCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbAccount call({dynamic serverAddress, dynamic userId}) {
|
||||||
|
return DbAccount(
|
||||||
|
serverAddress: serverAddress as String? ?? that.serverAddress,
|
||||||
|
userId: userId as CiString? ?? that.userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbAccount that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbAccountCopyWith on DbAccount {
|
||||||
|
$DbAccountCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbAccountCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbAccountCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbAlbumCopyWithWorker {
|
||||||
|
DbAlbum call(
|
||||||
|
{int? fileId,
|
||||||
|
String? fileEtag,
|
||||||
|
int? version,
|
||||||
|
DateTime? lastUpdated,
|
||||||
|
String? name,
|
||||||
|
String? providerType,
|
||||||
|
JsonObj? providerContent,
|
||||||
|
String? coverProviderType,
|
||||||
|
JsonObj? coverProviderContent,
|
||||||
|
String? sortProviderType,
|
||||||
|
JsonObj? sortProviderContent,
|
||||||
|
List<DbAlbumShare>? shares});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbAlbumCopyWithWorkerImpl implements $DbAlbumCopyWithWorker {
|
||||||
|
_$DbAlbumCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbAlbum call(
|
||||||
|
{dynamic fileId,
|
||||||
|
dynamic fileEtag = copyWithNull,
|
||||||
|
dynamic version,
|
||||||
|
dynamic lastUpdated,
|
||||||
|
dynamic name,
|
||||||
|
dynamic providerType,
|
||||||
|
dynamic providerContent,
|
||||||
|
dynamic coverProviderType,
|
||||||
|
dynamic coverProviderContent,
|
||||||
|
dynamic sortProviderType,
|
||||||
|
dynamic sortProviderContent,
|
||||||
|
dynamic shares}) {
|
||||||
|
return DbAlbum(
|
||||||
|
fileId: fileId as int? ?? that.fileId,
|
||||||
|
fileEtag:
|
||||||
|
fileEtag == copyWithNull ? that.fileEtag : fileEtag as String?,
|
||||||
|
version: version as int? ?? that.version,
|
||||||
|
lastUpdated: lastUpdated as DateTime? ?? that.lastUpdated,
|
||||||
|
name: name as String? ?? that.name,
|
||||||
|
providerType: providerType as String? ?? that.providerType,
|
||||||
|
providerContent: providerContent as JsonObj? ?? that.providerContent,
|
||||||
|
coverProviderType:
|
||||||
|
coverProviderType as String? ?? that.coverProviderType,
|
||||||
|
coverProviderContent:
|
||||||
|
coverProviderContent as JsonObj? ?? that.coverProviderContent,
|
||||||
|
sortProviderType: sortProviderType as String? ?? that.sortProviderType,
|
||||||
|
sortProviderContent:
|
||||||
|
sortProviderContent as JsonObj? ?? that.sortProviderContent,
|
||||||
|
shares: shares as List<DbAlbumShare>? ?? that.shares);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbAlbum that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbAlbumCopyWith on DbAlbum {
|
||||||
|
$DbAlbumCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbAlbumCopyWithWorker get _$copyWith => _$DbAlbumCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbAlbumShareCopyWithWorker {
|
||||||
|
DbAlbumShare call({String? userId, String? displayName, DateTime? sharedAt});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbAlbumShareCopyWithWorkerImpl implements $DbAlbumShareCopyWithWorker {
|
||||||
|
_$DbAlbumShareCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbAlbumShare call(
|
||||||
|
{dynamic userId, dynamic displayName = copyWithNull, dynamic sharedAt}) {
|
||||||
|
return DbAlbumShare(
|
||||||
|
userId: userId as String? ?? that.userId,
|
||||||
|
displayName: displayName == copyWithNull
|
||||||
|
? that.displayName
|
||||||
|
: displayName as String?,
|
||||||
|
sharedAt: sharedAt as DateTime? ?? that.sharedAt);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbAlbumShare that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbAlbumShareCopyWith on DbAlbumShare {
|
||||||
|
$DbAlbumShareCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbAlbumShareCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbAlbumShareCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbFaceRecognitionPersonCopyWithWorker {
|
||||||
|
DbFaceRecognitionPerson call({String? name, int? thumbFaceId, int? count});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbFaceRecognitionPersonCopyWithWorkerImpl
|
||||||
|
implements $DbFaceRecognitionPersonCopyWithWorker {
|
||||||
|
_$DbFaceRecognitionPersonCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbFaceRecognitionPerson call(
|
||||||
|
{dynamic name, dynamic thumbFaceId, dynamic count}) {
|
||||||
|
return DbFaceRecognitionPerson(
|
||||||
|
name: name as String? ?? that.name,
|
||||||
|
thumbFaceId: thumbFaceId as int? ?? that.thumbFaceId,
|
||||||
|
count: count as int? ?? that.count);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbFaceRecognitionPerson that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbFaceRecognitionPersonCopyWith on DbFaceRecognitionPerson {
|
||||||
|
$DbFaceRecognitionPersonCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbFaceRecognitionPersonCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbFaceRecognitionPersonCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbFileCopyWithWorker {
|
||||||
|
DbFile call(
|
||||||
|
{int? fileId,
|
||||||
|
int? contentLength,
|
||||||
|
String? contentType,
|
||||||
|
String? etag,
|
||||||
|
DateTime? lastModified,
|
||||||
|
bool? isCollection,
|
||||||
|
int? usedBytes,
|
||||||
|
bool? hasPreview,
|
||||||
|
CiString? ownerId,
|
||||||
|
String? ownerDisplayName,
|
||||||
|
String? relativePath,
|
||||||
|
bool? isFavorite,
|
||||||
|
bool? isArchived,
|
||||||
|
DateTime? overrideDateTime,
|
||||||
|
DateTime? bestDateTime,
|
||||||
|
DbImageData? imageData,
|
||||||
|
DbLocation? location,
|
||||||
|
DbTrashData? trashData});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbFileCopyWithWorkerImpl implements $DbFileCopyWithWorker {
|
||||||
|
_$DbFileCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbFile call(
|
||||||
|
{dynamic fileId,
|
||||||
|
dynamic contentLength = copyWithNull,
|
||||||
|
dynamic contentType = copyWithNull,
|
||||||
|
dynamic etag = copyWithNull,
|
||||||
|
dynamic lastModified = copyWithNull,
|
||||||
|
dynamic isCollection = copyWithNull,
|
||||||
|
dynamic usedBytes = copyWithNull,
|
||||||
|
dynamic hasPreview = copyWithNull,
|
||||||
|
dynamic ownerId = copyWithNull,
|
||||||
|
dynamic ownerDisplayName = copyWithNull,
|
||||||
|
dynamic relativePath,
|
||||||
|
dynamic isFavorite = copyWithNull,
|
||||||
|
dynamic isArchived = copyWithNull,
|
||||||
|
dynamic overrideDateTime = copyWithNull,
|
||||||
|
dynamic bestDateTime,
|
||||||
|
dynamic imageData = copyWithNull,
|
||||||
|
dynamic location = copyWithNull,
|
||||||
|
dynamic trashData = copyWithNull}) {
|
||||||
|
return DbFile(
|
||||||
|
fileId: fileId as int? ?? that.fileId,
|
||||||
|
contentLength: contentLength == copyWithNull
|
||||||
|
? that.contentLength
|
||||||
|
: contentLength as int?,
|
||||||
|
contentType: contentType == copyWithNull
|
||||||
|
? that.contentType
|
||||||
|
: contentType as String?,
|
||||||
|
etag: etag == copyWithNull ? that.etag : etag as String?,
|
||||||
|
lastModified: lastModified == copyWithNull
|
||||||
|
? that.lastModified
|
||||||
|
: lastModified as DateTime?,
|
||||||
|
isCollection: isCollection == copyWithNull
|
||||||
|
? that.isCollection
|
||||||
|
: isCollection as bool?,
|
||||||
|
usedBytes:
|
||||||
|
usedBytes == copyWithNull ? that.usedBytes : usedBytes as int?,
|
||||||
|
hasPreview:
|
||||||
|
hasPreview == copyWithNull ? that.hasPreview : hasPreview as bool?,
|
||||||
|
ownerId: ownerId == copyWithNull ? that.ownerId : ownerId as CiString?,
|
||||||
|
ownerDisplayName: ownerDisplayName == copyWithNull
|
||||||
|
? that.ownerDisplayName
|
||||||
|
: ownerDisplayName as String?,
|
||||||
|
relativePath: relativePath as String? ?? that.relativePath,
|
||||||
|
isFavorite:
|
||||||
|
isFavorite == copyWithNull ? that.isFavorite : isFavorite as bool?,
|
||||||
|
isArchived:
|
||||||
|
isArchived == copyWithNull ? that.isArchived : isArchived as bool?,
|
||||||
|
overrideDateTime: overrideDateTime == copyWithNull
|
||||||
|
? that.overrideDateTime
|
||||||
|
: overrideDateTime as DateTime?,
|
||||||
|
bestDateTime: bestDateTime as DateTime? ?? that.bestDateTime,
|
||||||
|
imageData: imageData == copyWithNull
|
||||||
|
? that.imageData
|
||||||
|
: imageData as DbImageData?,
|
||||||
|
location:
|
||||||
|
location == copyWithNull ? that.location : location as DbLocation?,
|
||||||
|
trashData: trashData == copyWithNull
|
||||||
|
? that.trashData
|
||||||
|
: trashData as DbTrashData?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbFile that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbFileCopyWith on DbFile {
|
||||||
|
$DbFileCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbFileCopyWithWorker get _$copyWith => _$DbFileCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbFileDescriptorCopyWithWorker {
|
||||||
|
DbFileDescriptor call(
|
||||||
|
{String? relativePath,
|
||||||
|
int? fileId,
|
||||||
|
String? contentType,
|
||||||
|
bool? isArchived,
|
||||||
|
bool? isFavorite,
|
||||||
|
DateTime? bestDateTime});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbFileDescriptorCopyWithWorkerImpl
|
||||||
|
implements $DbFileDescriptorCopyWithWorker {
|
||||||
|
_$DbFileDescriptorCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbFileDescriptor call(
|
||||||
|
{dynamic relativePath,
|
||||||
|
dynamic fileId,
|
||||||
|
dynamic contentType = copyWithNull,
|
||||||
|
dynamic isArchived = copyWithNull,
|
||||||
|
dynamic isFavorite = copyWithNull,
|
||||||
|
dynamic bestDateTime}) {
|
||||||
|
return DbFileDescriptor(
|
||||||
|
relativePath: relativePath as String? ?? that.relativePath,
|
||||||
|
fileId: fileId as int? ?? that.fileId,
|
||||||
|
contentType: contentType == copyWithNull
|
||||||
|
? that.contentType
|
||||||
|
: contentType as String?,
|
||||||
|
isArchived:
|
||||||
|
isArchived == copyWithNull ? that.isArchived : isArchived as bool?,
|
||||||
|
isFavorite:
|
||||||
|
isFavorite == copyWithNull ? that.isFavorite : isFavorite as bool?,
|
||||||
|
bestDateTime: bestDateTime as DateTime? ?? that.bestDateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbFileDescriptor that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbFileDescriptorCopyWith on DbFileDescriptor {
|
||||||
|
$DbFileDescriptorCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbFileDescriptorCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbFileDescriptorCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbImageDataCopyWithWorker {
|
||||||
|
DbImageData call(
|
||||||
|
{DateTime? lastUpdated,
|
||||||
|
String? fileEtag,
|
||||||
|
int? width,
|
||||||
|
int? height,
|
||||||
|
JsonObj? exif,
|
||||||
|
DateTime? exifDateTimeOriginal});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbImageDataCopyWithWorkerImpl implements $DbImageDataCopyWithWorker {
|
||||||
|
_$DbImageDataCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbImageData call(
|
||||||
|
{dynamic lastUpdated,
|
||||||
|
dynamic fileEtag = copyWithNull,
|
||||||
|
dynamic width = copyWithNull,
|
||||||
|
dynamic height = copyWithNull,
|
||||||
|
dynamic exif = copyWithNull,
|
||||||
|
dynamic exifDateTimeOriginal = copyWithNull}) {
|
||||||
|
return DbImageData(
|
||||||
|
lastUpdated: lastUpdated as DateTime? ?? that.lastUpdated,
|
||||||
|
fileEtag:
|
||||||
|
fileEtag == copyWithNull ? that.fileEtag : fileEtag as String?,
|
||||||
|
width: width == copyWithNull ? that.width : width as int?,
|
||||||
|
height: height == copyWithNull ? that.height : height as int?,
|
||||||
|
exif: exif == copyWithNull ? that.exif : exif as JsonObj?,
|
||||||
|
exifDateTimeOriginal: exifDateTimeOriginal == copyWithNull
|
||||||
|
? that.exifDateTimeOriginal
|
||||||
|
: exifDateTimeOriginal as DateTime?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbImageData that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbImageDataCopyWith on DbImageData {
|
||||||
|
$DbImageDataCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbImageDataCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbImageDataCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbLocationCopyWithWorker {
|
||||||
|
DbLocation call(
|
||||||
|
{int? version,
|
||||||
|
String? name,
|
||||||
|
double? latitude,
|
||||||
|
double? longitude,
|
||||||
|
String? countryCode,
|
||||||
|
String? admin1,
|
||||||
|
String? admin2});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbLocationCopyWithWorkerImpl implements $DbLocationCopyWithWorker {
|
||||||
|
_$DbLocationCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbLocation call(
|
||||||
|
{dynamic version,
|
||||||
|
dynamic name = copyWithNull,
|
||||||
|
dynamic latitude = copyWithNull,
|
||||||
|
dynamic longitude = copyWithNull,
|
||||||
|
dynamic countryCode = copyWithNull,
|
||||||
|
dynamic admin1 = copyWithNull,
|
||||||
|
dynamic admin2 = copyWithNull}) {
|
||||||
|
return DbLocation(
|
||||||
|
version: version as int? ?? that.version,
|
||||||
|
name: name == copyWithNull ? that.name : name as String?,
|
||||||
|
latitude:
|
||||||
|
latitude == copyWithNull ? that.latitude : latitude as double?,
|
||||||
|
longitude:
|
||||||
|
longitude == copyWithNull ? that.longitude : longitude as double?,
|
||||||
|
countryCode: countryCode == copyWithNull
|
||||||
|
? that.countryCode
|
||||||
|
: countryCode as String?,
|
||||||
|
admin1: admin1 == copyWithNull ? that.admin1 : admin1 as String?,
|
||||||
|
admin2: admin2 == copyWithNull ? that.admin2 : admin2 as String?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbLocation that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbLocationCopyWith on DbLocation {
|
||||||
|
$DbLocationCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbLocationCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbLocationCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbNcAlbumCopyWithWorker {
|
||||||
|
DbNcAlbum call(
|
||||||
|
{String? relativePath,
|
||||||
|
int? lastPhoto,
|
||||||
|
int? nbItems,
|
||||||
|
String? location,
|
||||||
|
DateTime? dateStart,
|
||||||
|
DateTime? dateEnd,
|
||||||
|
List<JsonObj>? collaborators,
|
||||||
|
bool? isOwned});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbNcAlbumCopyWithWorkerImpl implements $DbNcAlbumCopyWithWorker {
|
||||||
|
_$DbNcAlbumCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbNcAlbum call(
|
||||||
|
{dynamic relativePath,
|
||||||
|
dynamic lastPhoto = copyWithNull,
|
||||||
|
dynamic nbItems,
|
||||||
|
dynamic location = copyWithNull,
|
||||||
|
dynamic dateStart = copyWithNull,
|
||||||
|
dynamic dateEnd = copyWithNull,
|
||||||
|
dynamic collaborators,
|
||||||
|
dynamic isOwned}) {
|
||||||
|
return DbNcAlbum(
|
||||||
|
relativePath: relativePath as String? ?? that.relativePath,
|
||||||
|
lastPhoto:
|
||||||
|
lastPhoto == copyWithNull ? that.lastPhoto : lastPhoto as int?,
|
||||||
|
nbItems: nbItems as int? ?? that.nbItems,
|
||||||
|
location:
|
||||||
|
location == copyWithNull ? that.location : location as String?,
|
||||||
|
dateStart:
|
||||||
|
dateStart == copyWithNull ? that.dateStart : dateStart as DateTime?,
|
||||||
|
dateEnd: dateEnd == copyWithNull ? that.dateEnd : dateEnd as DateTime?,
|
||||||
|
collaborators: collaborators as List<JsonObj>? ?? that.collaborators,
|
||||||
|
isOwned: isOwned as bool? ?? that.isOwned);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbNcAlbum that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbNcAlbumCopyWith on DbNcAlbum {
|
||||||
|
$DbNcAlbumCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbNcAlbumCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbNcAlbumCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbNcAlbumItemCopyWithWorker {
|
||||||
|
DbNcAlbumItem call(
|
||||||
|
{String? relativePath,
|
||||||
|
int? fileId,
|
||||||
|
int? contentLength,
|
||||||
|
String? contentType,
|
||||||
|
String? etag,
|
||||||
|
DateTime? lastModified,
|
||||||
|
bool? hasPreview,
|
||||||
|
bool? isFavorite,
|
||||||
|
int? fileMetadataWidth,
|
||||||
|
int? fileMetadataHeight});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbNcAlbumItemCopyWithWorkerImpl
|
||||||
|
implements $DbNcAlbumItemCopyWithWorker {
|
||||||
|
_$DbNcAlbumItemCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbNcAlbumItem call(
|
||||||
|
{dynamic relativePath,
|
||||||
|
dynamic fileId,
|
||||||
|
dynamic contentLength = copyWithNull,
|
||||||
|
dynamic contentType = copyWithNull,
|
||||||
|
dynamic etag = copyWithNull,
|
||||||
|
dynamic lastModified = copyWithNull,
|
||||||
|
dynamic hasPreview = copyWithNull,
|
||||||
|
dynamic isFavorite = copyWithNull,
|
||||||
|
dynamic fileMetadataWidth = copyWithNull,
|
||||||
|
dynamic fileMetadataHeight = copyWithNull}) {
|
||||||
|
return DbNcAlbumItem(
|
||||||
|
relativePath: relativePath as String? ?? that.relativePath,
|
||||||
|
fileId: fileId as int? ?? that.fileId,
|
||||||
|
contentLength: contentLength == copyWithNull
|
||||||
|
? that.contentLength
|
||||||
|
: contentLength as int?,
|
||||||
|
contentType: contentType == copyWithNull
|
||||||
|
? that.contentType
|
||||||
|
: contentType as String?,
|
||||||
|
etag: etag == copyWithNull ? that.etag : etag as String?,
|
||||||
|
lastModified: lastModified == copyWithNull
|
||||||
|
? that.lastModified
|
||||||
|
: lastModified as DateTime?,
|
||||||
|
hasPreview:
|
||||||
|
hasPreview == copyWithNull ? that.hasPreview : hasPreview as bool?,
|
||||||
|
isFavorite:
|
||||||
|
isFavorite == copyWithNull ? that.isFavorite : isFavorite as bool?,
|
||||||
|
fileMetadataWidth: fileMetadataWidth == copyWithNull
|
||||||
|
? that.fileMetadataWidth
|
||||||
|
: fileMetadataWidth as int?,
|
||||||
|
fileMetadataHeight: fileMetadataHeight == copyWithNull
|
||||||
|
? that.fileMetadataHeight
|
||||||
|
: fileMetadataHeight as int?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbNcAlbumItem that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbNcAlbumItemCopyWith on DbNcAlbumItem {
|
||||||
|
$DbNcAlbumItemCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbNcAlbumItemCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbNcAlbumItemCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbRecognizeFaceCopyWithWorker {
|
||||||
|
DbRecognizeFace call({String? label});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbRecognizeFaceCopyWithWorkerImpl
|
||||||
|
implements $DbRecognizeFaceCopyWithWorker {
|
||||||
|
_$DbRecognizeFaceCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbRecognizeFace call({dynamic label}) {
|
||||||
|
return DbRecognizeFace(label: label as String? ?? that.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbRecognizeFace that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbRecognizeFaceCopyWith on DbRecognizeFace {
|
||||||
|
$DbRecognizeFaceCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbRecognizeFaceCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbRecognizeFaceCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbRecognizeFaceItemCopyWithWorker {
|
||||||
|
DbRecognizeFaceItem call(
|
||||||
|
{String? relativePath,
|
||||||
|
int? fileId,
|
||||||
|
int? contentLength,
|
||||||
|
String? contentType,
|
||||||
|
String? etag,
|
||||||
|
DateTime? lastModified,
|
||||||
|
bool? hasPreview,
|
||||||
|
String? realPath,
|
||||||
|
bool? isFavorite,
|
||||||
|
int? fileMetadataWidth,
|
||||||
|
int? fileMetadataHeight,
|
||||||
|
String? faceDetections});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbRecognizeFaceItemCopyWithWorkerImpl
|
||||||
|
implements $DbRecognizeFaceItemCopyWithWorker {
|
||||||
|
_$DbRecognizeFaceItemCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbRecognizeFaceItem call(
|
||||||
|
{dynamic relativePath,
|
||||||
|
dynamic fileId,
|
||||||
|
dynamic contentLength = copyWithNull,
|
||||||
|
dynamic contentType = copyWithNull,
|
||||||
|
dynamic etag = copyWithNull,
|
||||||
|
dynamic lastModified = copyWithNull,
|
||||||
|
dynamic hasPreview = copyWithNull,
|
||||||
|
dynamic realPath = copyWithNull,
|
||||||
|
dynamic isFavorite = copyWithNull,
|
||||||
|
dynamic fileMetadataWidth = copyWithNull,
|
||||||
|
dynamic fileMetadataHeight = copyWithNull,
|
||||||
|
dynamic faceDetections = copyWithNull}) {
|
||||||
|
return DbRecognizeFaceItem(
|
||||||
|
relativePath: relativePath as String? ?? that.relativePath,
|
||||||
|
fileId: fileId as int? ?? that.fileId,
|
||||||
|
contentLength: contentLength == copyWithNull
|
||||||
|
? that.contentLength
|
||||||
|
: contentLength as int?,
|
||||||
|
contentType: contentType == copyWithNull
|
||||||
|
? that.contentType
|
||||||
|
: contentType as String?,
|
||||||
|
etag: etag == copyWithNull ? that.etag : etag as String?,
|
||||||
|
lastModified: lastModified == copyWithNull
|
||||||
|
? that.lastModified
|
||||||
|
: lastModified as DateTime?,
|
||||||
|
hasPreview:
|
||||||
|
hasPreview == copyWithNull ? that.hasPreview : hasPreview as bool?,
|
||||||
|
realPath:
|
||||||
|
realPath == copyWithNull ? that.realPath : realPath as String?,
|
||||||
|
isFavorite:
|
||||||
|
isFavorite == copyWithNull ? that.isFavorite : isFavorite as bool?,
|
||||||
|
fileMetadataWidth: fileMetadataWidth == copyWithNull
|
||||||
|
? that.fileMetadataWidth
|
||||||
|
: fileMetadataWidth as int?,
|
||||||
|
fileMetadataHeight: fileMetadataHeight == copyWithNull
|
||||||
|
? that.fileMetadataHeight
|
||||||
|
: fileMetadataHeight as int?,
|
||||||
|
faceDetections: faceDetections == copyWithNull
|
||||||
|
? that.faceDetections
|
||||||
|
: faceDetections as String?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbRecognizeFaceItem that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbRecognizeFaceItemCopyWith on DbRecognizeFaceItem {
|
||||||
|
$DbRecognizeFaceItemCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbRecognizeFaceItemCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbRecognizeFaceItemCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbTagCopyWithWorker {
|
||||||
|
DbTag call(
|
||||||
|
{int? id, String? displayName, bool? userVisible, bool? userAssignable});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbTagCopyWithWorkerImpl implements $DbTagCopyWithWorker {
|
||||||
|
_$DbTagCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbTag call(
|
||||||
|
{dynamic id,
|
||||||
|
dynamic displayName,
|
||||||
|
dynamic userVisible = copyWithNull,
|
||||||
|
dynamic userAssignable = copyWithNull}) {
|
||||||
|
return DbTag(
|
||||||
|
id: id as int? ?? that.id,
|
||||||
|
displayName: displayName as String? ?? that.displayName,
|
||||||
|
userVisible: userVisible == copyWithNull
|
||||||
|
? that.userVisible
|
||||||
|
: userVisible as bool?,
|
||||||
|
userAssignable: userAssignable == copyWithNull
|
||||||
|
? that.userAssignable
|
||||||
|
: userAssignable as bool?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbTag that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbTagCopyWith on DbTag {
|
||||||
|
$DbTagCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbTagCopyWithWorker get _$copyWith => _$DbTagCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class $DbTrashDataCopyWithWorker {
|
||||||
|
DbTrashData call(
|
||||||
|
{String? filename, String? originalLocation, DateTime? deletionTime});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$DbTrashDataCopyWithWorkerImpl implements $DbTrashDataCopyWithWorker {
|
||||||
|
_$DbTrashDataCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
DbTrashData call(
|
||||||
|
{dynamic filename, dynamic originalLocation, dynamic deletionTime}) {
|
||||||
|
return DbTrashData(
|
||||||
|
filename: filename as String? ?? that.filename,
|
||||||
|
originalLocation: originalLocation as String? ?? that.originalLocation,
|
||||||
|
deletionTime: deletionTime as DateTime? ?? that.deletionTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
final DbTrashData that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $DbTrashDataCopyWith on DbTrashData {
|
||||||
|
$DbTrashDataCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$DbTrashDataCopyWithWorker get _$copyWith =>
|
||||||
|
_$DbTrashDataCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$DbAccountToString on DbAccount {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbAccount {serverAddress: $serverAddress, userId: $userId}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbAlbumToString on DbAlbum {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbAlbum {fileId: $fileId, fileEtag: $fileEtag, version: $version, lastUpdated: $lastUpdated, name: $name, providerType: $providerType, providerContent: $providerContent, coverProviderType: $coverProviderType, coverProviderContent: $coverProviderContent, sortProviderType: $sortProviderType, sortProviderContent: $sortProviderContent, shares: [length: ${shares.length}]}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbAlbumShareToString on DbAlbumShare {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbAlbumShare {userId: $userId, displayName: $displayName, sharedAt: $sharedAt}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbFaceRecognitionPersonToString on DbFaceRecognitionPerson {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbFaceRecognitionPerson {name: $name, thumbFaceId: $thumbFaceId, count: $count}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbFileToString on DbFile {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbFile {fileId: $fileId, contentLength: $contentLength, contentType: $contentType, etag: $etag, lastModified: $lastModified, isCollection: $isCollection, usedBytes: $usedBytes, hasPreview: $hasPreview, ownerId: $ownerId, ownerDisplayName: $ownerDisplayName, relativePath: $relativePath, isFavorite: $isFavorite, isArchived: $isArchived, overrideDateTime: $overrideDateTime, bestDateTime: $bestDateTime, imageData: $imageData, location: $location, trashData: $trashData}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbFileDescriptorToString on DbFileDescriptor {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbFileDescriptor {relativePath: $relativePath, fileId: $fileId, contentType: $contentType, isArchived: $isArchived, isFavorite: $isFavorite, bestDateTime: $bestDateTime}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbImageDataToString on DbImageData {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbImageData {lastUpdated: $lastUpdated, fileEtag: $fileEtag, width: $width, height: $height, exif: $exif, exifDateTimeOriginal: $exifDateTimeOriginal}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbLocationToString on DbLocation {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbLocation {version: $version, name: $name, latitude: ${latitude == null ? null : "${latitude!.toStringAsFixed(3)}"}, longitude: ${longitude == null ? null : "${longitude!.toStringAsFixed(3)}"}, countryCode: $countryCode, admin1: $admin1, admin2: $admin2}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbNcAlbumToString on DbNcAlbum {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbNcAlbum {relativePath: $relativePath, lastPhoto: $lastPhoto, nbItems: $nbItems, location: $location, dateStart: $dateStart, dateEnd: $dateEnd, collaborators: [length: ${collaborators.length}], isOwned: $isOwned}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbNcAlbumItemToString on DbNcAlbumItem {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbNcAlbumItem {relativePath: $relativePath, fileId: $fileId, contentLength: $contentLength, contentType: $contentType, etag: $etag, lastModified: $lastModified, hasPreview: $hasPreview, isFavorite: $isFavorite, fileMetadataWidth: $fileMetadataWidth, fileMetadataHeight: $fileMetadataHeight}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbRecognizeFaceToString on DbRecognizeFace {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbRecognizeFace {label: $label}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbRecognizeFaceItemToString on DbRecognizeFaceItem {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbRecognizeFaceItem {relativePath: $relativePath, fileId: $fileId, contentLength: $contentLength, contentType: $contentType, etag: $etag, lastModified: $lastModified, hasPreview: $hasPreview, realPath: $realPath, isFavorite: $isFavorite, fileMetadataWidth: $fileMetadataWidth, fileMetadataHeight: $fileMetadataHeight, faceDetections: $faceDetections}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbTagToString on DbTag {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbTag {id: $id, displayName: $displayName, userVisible: $userVisible, userAssignable: $userAssignable}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$DbTrashDataToString on DbTrashData {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbTrashData {filename: $filename, originalLocation: $originalLocation, deletionTime: $deletionTime}";
|
||||||
|
}
|
||||||
|
}
|
13
np_db/lib/src/exception.dart
Normal file
13
np_db/lib/src/exception.dart
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'exception.g.dart';
|
||||||
|
|
||||||
|
@ToString(ignoreNull: true)
|
||||||
|
class DbNotFoundException implements Exception {
|
||||||
|
const DbNotFoundException([this.message]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final String? message;
|
||||||
|
}
|
14
np_db/lib/src/exception.g.dart
Normal file
14
np_db/lib/src/exception.g.dart
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'exception.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$DbNotFoundExceptionToString on DbNotFoundException {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "DbNotFoundException {${message == null ? "" : "message: $message"}}";
|
||||||
|
}
|
||||||
|
}
|
53
np_db/pubspec.yaml
Normal file
53
np_db/pubspec.yaml
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
name: np_db
|
||||||
|
description: A starting point for Dart libraries or applications.
|
||||||
|
version: 1.0.0
|
||||||
|
# repository: https://github.com/my_org/my_repo
|
||||||
|
publish_to: 'none'
|
||||||
|
|
||||||
|
environment:
|
||||||
|
sdk: '>=2.19.6 <3.0.0'
|
||||||
|
flutter: ">=3.7.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
copy_with:
|
||||||
|
git:
|
||||||
|
url: https://gitlab.com/nkming2/dart-copy-with
|
||||||
|
path: copy_with
|
||||||
|
ref: copy_with-1.3.0
|
||||||
|
equatable: ^2.0.5
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
logging: ^1.1.1
|
||||||
|
np_codegen:
|
||||||
|
path: ../codegen
|
||||||
|
np_common:
|
||||||
|
path: ../np_common
|
||||||
|
np_datetime:
|
||||||
|
path: ../np_datetime
|
||||||
|
np_db_sqlite:
|
||||||
|
path: ../np_db_sqlite
|
||||||
|
np_string:
|
||||||
|
path: ../np_string
|
||||||
|
to_string:
|
||||||
|
git:
|
||||||
|
url: https://gitlab.com/nkming2/dart-to-string
|
||||||
|
ref: to_string-1.0.0
|
||||||
|
path: to_string
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
build_runner: ^2.2.1
|
||||||
|
copy_with_build:
|
||||||
|
git:
|
||||||
|
url: https://gitlab.com/nkming2/dart-copy-with
|
||||||
|
path: copy_with_build
|
||||||
|
ref: copy_with_build-1.7.0
|
||||||
|
np_codegen_build:
|
||||||
|
path: ../codegen_build
|
||||||
|
np_lints:
|
||||||
|
path: ../np_lints
|
||||||
|
test: ^1.21.0
|
||||||
|
to_string_build:
|
||||||
|
git:
|
||||||
|
url: https://gitlab.com/nkming2/dart-to-string
|
||||||
|
ref: to_string_build-1.0.0
|
||||||
|
path: to_string_build
|
32
np_db_sqlite/.gitignore
vendored
Normal file
32
np_db_sqlite/.gitignore
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
migrate_working_dir/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
/pubspec.lock
|
||||||
|
**/doc/api/
|
||||||
|
.dart_tool/
|
||||||
|
.packages
|
||||||
|
build/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
10
np_db_sqlite/.metadata
Normal file
10
np_db_sqlite/.metadata
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: package
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue