nc-photos/app/lib/use_case/sync_dir.dart

144 lines
4.7 KiB
Dart
Raw Normal View History

import 'package:flutter/rendering.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/debug_util.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/file.dart';
import 'package:nc_photos/entity/file/data_source.dart';
import 'package:nc_photos/entity/file_descriptor.dart';
2023-02-20 15:21:35 +01:00
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/remote_storage_util.dart' as remote_storage_util;
import 'package:nc_photos/use_case/ls_single_file.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
import 'package:tuple/tuple.dart';
2022-12-16 16:01:04 +01:00
part 'sync_dir.g.dart';
@npLog
class SyncDir {
SyncDir(this._c) : assert(require(_c));
static bool require(DiContainer c) =>
DiContainer.has(c, DiType.fileRepoRemote) &&
DiContainer.has(c, DiType.sqliteDb) &&
DiContainer.has(c, DiType.touchManager);
/// Sync local SQLite DB with remote content
///
/// Return true if some of the files have changed
Future<bool> call(
Account account,
String dirPath, {
bool isRecursive = true,
ValueChanged<Progress>? onProgressUpdate,
}) async {
final dirCache = await _queryAllDirEtags(account, dirPath);
final remoteRoot =
await LsSingleFile(_c.withRemoteFileRepo())(account, dirPath);
return await _syncDir(
account,
remoteRoot,
dirCache,
isRecursive: isRecursive,
onProgressUpdate: onProgressUpdate,
);
}
Future<bool> _syncDir(
Account account,
File remoteDir,
Map<int, String> dirCache, {
required bool isRecursive,
ValueChanged<Progress>? onProgressUpdate,
}) async {
final status = await _checkContentUpdated(account, remoteDir, dirCache);
if (!status.item1) {
_log.finer("[_syncDir] Dir unchanged: ${remoteDir.path}");
return false;
}
_log.info("[_syncDir] Dir changed: ${remoteDir.path}");
final dataSrc = FileCachedDataSource(_c, shouldCheckCache: true);
final syncState = await dataSrc.beginSync(account, remoteDir,
remoteTouchEtag: status.item2);
final children = syncState.files;
if (!isRecursive) {
await dataSrc.concludeSync(syncState);
return true;
}
final subDirs = children
.where((f) =>
f.isCollection == true &&
!remoteDir.compareServerIdentity(f) &&
!f.path.endsWith(remote_storage_util.getRemoteStorageDir(account)))
.toList();
final progress = IntProgress(subDirs.length);
for (final d in subDirs) {
onProgressUpdate
?.call(Progress(progress.progress, d.strippedPathWithEmpty));
try {
await _syncDir(
account,
d,
dirCache,
isRecursive: isRecursive,
onProgressUpdate: (value) {
final merged = progress.progress + progress.step * value.progress;
onProgressUpdate?.call(Progress(merged, value.text));
},
);
} catch (e, stackTrace) {
_log.severe("[_syncDir] Failed while _syncDir: ${logFilename(d.path)}",
e, stackTrace);
}
progress.next();
}
await dataSrc.concludeSync(syncState);
return true;
}
Future<Tuple2<bool, String?>> _checkContentUpdated(
Account account, File remoteDir, Map<int, String> dirCache) async {
String? touchResult;
try {
touchResult = await _c.touchManager.checkTouchEtag(account, remoteDir);
if (touchResult == null &&
dirCache[remoteDir.fileId!] == remoteDir.etag!) {
return const Tuple2(false, null);
}
} catch (e, stackTrace) {
_log.severe("[_isContentUpdated] Uncaught exception", e, stackTrace);
}
return Tuple2(true, touchResult);
}
Future<Map<int, String>> _queryAllDirEtags(
Account account, String dirPath) async {
final dir = File(path: dirPath);
return await _c.sqliteDb.use((db) async {
final query = db.queryFiles().run((q) {
q
..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;
}