Fix startup sync results not propagated to main isolate

This commit is contained in:
Ming Ming 2024-01-18 01:17:53 +08:00
parent a04ecefed9
commit f97476e4f0
13 changed files with 229 additions and 45 deletions

View file

@ -13,6 +13,7 @@ import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/progress_util.dart';
import 'package:nc_photos/rx_extension.dart';
import 'package:nc_photos/use_case/file/list_file.dart';
import 'package:nc_photos/use_case/find_file_descriptor.dart';
import 'package:nc_photos/use_case/remove.dart';
import 'package:nc_photos/use_case/sync_dir.dart';
import 'package:nc_photos/use_case/update_property.dart';
@ -20,6 +21,7 @@ import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/lazy.dart';
import 'package:np_common/object_util.dart';
import 'package:np_common/or_null.dart';
import 'package:np_db/np_db.dart';
import 'package:rxdart/rxdart.dart';
import 'package:to_string/to_string.dart';
@ -233,6 +235,69 @@ class FilesController {
}
}
Future<void> applySyncResult({
DbSyncIdResult? favorites,
List<int>? fileExifs,
}) async {
if (favorites?.isNotEmpty != true && fileExifs?.isNotEmpty != true) {
return;
}
// do async ops first
final fileExifFiles =
await fileExifs?.letFuture((e) async => await FindFileDescriptor(_c)(
account,
e,
onFileNotFound: (id) {
_log.warning("[applySyncResult] File id not found: $id");
},
));
final next = LinkedHashMap.of(_dataStreamController.value.files);
if (favorites != null && favorites.isNotEmpty) {
_applySyncFavoriteResult(next, favorites);
}
if (fileExifFiles != null && fileExifFiles.isNotEmpty) {
_applySyncFileExifResult(next, fileExifFiles);
}
_dataStreamController.addWithValue((value) => value.copyWith(files: next));
}
void _applySyncFavoriteResult(
Map<int, FileDescriptor> next, DbSyncIdResult result) {
for (final id in result.insert) {
final f = next[id];
if (f == null) {
_log.warning("[_applySyncFavoriteResult] File id not found: $id");
continue;
}
if (f is File) {
next[id] = f.copyWith(isFavorite: true);
} else {
next[id] = f.copyWith(fdIsFavorite: true);
}
}
for (final id in result.delete) {
final f = next[id];
if (f == null) {
_log.warning("[_applySyncFavoriteResult] File id not found: $id");
continue;
}
if (f is File) {
next[id] = f.copyWith(isFavorite: false);
} else {
next[id] = f.copyWith(fdIsFavorite: false);
}
}
}
void _applySyncFileExifResult(
Map<int, FileDescriptor> next, List<FileDescriptor> results) {
for (final f in results) {
next[f.fdId] = f;
}
}
Future<void> _load() async {
var lastData = _FilesStreamEvent(
files: const {},

View file

@ -2,6 +2,8 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/controller/files_controller.dart';
import 'package:nc_photos/controller/persons_controller.dart';
import 'package:nc_photos/entity/person.dart';
import 'package:nc_photos/use_case/startup_sync.dart';
@ -15,14 +17,19 @@ class SyncController {
_isDisposed = true;
}
Future<void> requestSync(
Account account, PersonProvider personProvider) async {
Future<void> requestSync({
required Account account,
required FilesController filesController,
required PersonsController personsController,
required PersonProvider personProvider,
}) async {
if (_isDisposed) {
return;
}
if (_syncCompleter == null) {
_syncCompleter = Completer();
final result = await StartupSync.runInIsolate(account, personProvider);
final result = await StartupSync.runInIsolate(
account, filesController, personsController, personProvider);
if (!_isDisposed && result.isSyncPersonUpdated) {
onPeopleUpdated?.call();
}
@ -32,15 +39,23 @@ class SyncController {
}
}
Future<void> requestResync(
Account account, PersonProvider personProvider) async {
Future<void> requestResync({
required Account account,
required FilesController filesController,
required PersonsController personsController,
required PersonProvider personProvider,
}) async {
if (_syncCompleter?.isCompleted == true) {
_syncCompleter = null;
return requestSync(account, personProvider);
} else {
// already syncing
return requestSync(account, personProvider);
}
return requestSync(
account: account,
filesController: filesController,
personsController: personsController,
personProvider: personProvider,
);
}
final Account account;

View file

@ -6,6 +6,7 @@ import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/event/event.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_db/np_db.dart';
part 'cache_favorite.g.dart';
@ -15,18 +16,18 @@ class CacheFavorite {
/// Cache favorites using results from remote
///
/// Return number of files updated
Future<int> call(Account account, Iterable<int> remoteFileIds) async {
/// Return the fileIds of the affected files
Future<DbSyncIdResult> call(
Account account, Iterable<int> remoteFileIds) async {
_log.info("[call] Cache favorites");
final result = await _c.npDb.syncFavoriteFiles(
account: account.toDb(),
favoriteFileIds: remoteFileIds.toList(),
);
final count = result.insert + result.delete + result.update;
if (count > 0) {
if (result.isNotEmpty) {
KiwiContainer().resolve<EventBus>().fire(FavoriteResyncedEvent(account));
}
return count;
return result;
}
final DiContainer _c;

View file

@ -7,6 +7,8 @@ import 'package:logging/logging.dart';
import 'package:mutex/mutex.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_init.dart' as app_init;
import 'package:nc_photos/controller/files_controller.dart';
import 'package:nc_photos/controller/persons_controller.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/person.dart';
import 'package:nc_photos/event/event.dart';
@ -14,8 +16,11 @@ import 'package:nc_photos/use_case/person/sync_person.dart';
import 'package:nc_photos/use_case/sync_favorite.dart';
import 'package:nc_photos/use_case/sync_tag.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/object_util.dart';
import 'package:np_common/type.dart';
import 'package:np_db/np_db.dart';
import 'package:np_platform_util/np_platform_util.dart';
import 'package:to_string/to_string.dart';
part 'startup_sync.g.dart';
@ -28,7 +33,11 @@ class StartupSync {
/// Sync in a background isolate
static Future<SyncResult> runInIsolate(
Account account, PersonProvider personProvider) async {
Account account,
FilesController filesController,
PersonsController personsController,
PersonProvider personProvider,
) async {
return _mutex.protect(() async {
if (getRawPlatform() == NpPlatform.web) {
// not supported on web
@ -42,7 +51,7 @@ class StartupSync {
final result = SyncResult.fromJson(resultJson);
// events fired in background isolate won't be noticed by the main isolate,
// so we fire them again here
_broadcastResult(account, result);
_broadcastResult(account, filesController, personsController, result);
return result;
}
});
@ -52,16 +61,16 @@ class StartupSync {
Account account, PersonProvider personProvider) async {
_log.info("[_run] Begin sync");
final stopwatch = Stopwatch()..start();
late final int syncFavoriteCount;
late final bool isSyncPersonUpdated;
DbSyncIdResult? syncFavoriteResult;
DbSyncIdResult? syncTagResult;
var isSyncPersonUpdated = false;
try {
syncFavoriteCount = await SyncFavorite(_c)(account);
syncFavoriteResult = await SyncFavorite(_c)(account);
} catch (e, stackTrace) {
_log.shout("[_run] Failed while SyncFavorite", e, stackTrace);
syncFavoriteCount = -1;
}
try {
await SyncTag(_c)(account);
syncTagResult = await SyncTag(_c)(account);
} catch (e, stackTrace) {
_log.shout("[_run] Failed while SyncTag", e, stackTrace);
}
@ -72,16 +81,28 @@ class StartupSync {
}
_log.info("[_run] Elapsed time: ${stopwatch.elapsedMilliseconds}ms");
return SyncResult(
syncFavoriteCount: syncFavoriteCount,
syncFavoriteResult: syncFavoriteResult,
syncTagResult: syncTagResult,
isSyncPersonUpdated: isSyncPersonUpdated,
);
}
static void _broadcastResult(Account account, SyncResult result) {
final eventBus = KiwiContainer().resolve<EventBus>();
if (result.syncFavoriteCount > 0) {
static void _broadcastResult(
Account account,
FilesController filesController,
PersonsController personsController,
SyncResult result,
) {
_$StartupSyncNpLog.log.info('[_broadcastResult] $result');
if (result.syncFavoriteResult != null) {
filesController.applySyncResult(favorites: result.syncFavoriteResult!);
// legacy
final eventBus = KiwiContainer().resolve<EventBus>();
eventBus.fire(FavoriteResyncedEvent(account));
}
if (result.isSyncPersonUpdated) {
personsController.reload();
}
}
final DiContainer _c;
@ -89,23 +110,35 @@ class StartupSync {
static final _mutex = Mutex();
}
@toString
class SyncResult {
const SyncResult({
required this.syncFavoriteCount,
required this.syncFavoriteResult,
required this.syncTagResult,
required this.isSyncPersonUpdated,
});
factory SyncResult.fromJson(JsonObj json) => SyncResult(
syncFavoriteCount: json["syncFavoriteCount"],
syncFavoriteResult: (json["syncFavoriteResult"] as Map?)
?.cast<String, dynamic>()
.let(DbSyncIdResult.fromJson),
syncTagResult: (json["syncTagResult"] as Map?)
?.cast<String, dynamic>()
.let(DbSyncIdResult.fromJson),
isSyncPersonUpdated: json["isSyncPersonUpdated"],
);
JsonObj toJson() => {
"syncFavoriteCount": syncFavoriteCount,
"syncFavoriteResult": syncFavoriteResult?.toJson(),
"syncTagResult": syncTagResult?.toJson(),
"isSyncPersonUpdated": isSyncPersonUpdated,
};
final int syncFavoriteCount;
@override
String toString() => _$toString();
final DbSyncIdResult? syncFavoriteResult;
final DbSyncIdResult? syncTagResult;
final bool isSyncPersonUpdated;
}

View file

@ -12,3 +12,14 @@ extension _$StartupSyncNpLog on StartupSync {
static final log = Logger("use_case.startup_sync.StartupSync");
}
// **************************************************************************
// ToStringGenerator
// **************************************************************************
extension _$SyncResultToString on SyncResult {
String _$toString() {
// ignore: unnecessary_string_interpolations
return "SyncResult {syncFavoriteResult: $syncFavoriteResult, syncTagResult: $syncTagResult, isSyncPersonUpdated: $isSyncPersonUpdated}";
}
}

View file

@ -6,6 +6,7 @@ import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/entity/pref.dart';
import 'package:nc_photos/use_case/cache_favorite.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_db/np_db.dart';
part 'sync_favorite.g.dart';
@ -18,7 +19,7 @@ class SyncFavorite {
/// Sync favorites in cache db with remote server
///
/// Return number of files updated
Future<int> call(Account account) async {
Future<DbSyncIdResult> call(Account account) async {
_log.info("[call] Sync favorites with remote");
final remote = await _getRemoteFavoriteFileIds(account);
return await CacheFavorite(_c)(account, remote);

View file

@ -3,6 +3,7 @@ import 'package:nc_photos/account.dart';
import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/di_container.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_db/np_db.dart';
part 'sync_tag.g.dart';
@ -11,13 +12,16 @@ class SyncTag {
const SyncTag(this._c);
/// Sync tags in cache db with remote server
Future<void> call(Account account) async {
///
/// Return tagIds of the affected tags
Future<DbSyncIdResult> call(Account account) async {
_log.info("[call] Sync tags with remote");
final remote = await _c.tagRepoRemote.list(account);
await _c.npDb.syncTags(
final result = await _c.npDb.syncTags(
account: account.toDb(),
tags: remote.map(DbTagConverter.toDb).toList(),
);
return result;
}
final DiContainer _c;

View file

@ -609,7 +609,11 @@ class _HomePhotosState extends State<HomePhotos>
if (isPostSuccess) {
_isScrollbarVisible = true;
context.read<AccountController>().syncController.requestSync(
widget.account, _accountPrefController.personProvider.value);
account: widget.account,
filesController: context.read(),
personsController: context.read(),
personProvider: _accountPrefController.personProvider.value,
);
_tryStartMetadataTask(context);
}
});

View file

@ -10,6 +10,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
required this.accountPrefController,
required this.collectionsController,
required this.sessionController,
required this.syncController,
required this.personsController,
}) : super(_State.init(
zoom: prefController.homePhotosZoomLevel.value,
isEnableMemoryCollection:
@ -142,6 +144,12 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
memoryCollections: ev.memoryCollections,
isLoading: _itemTransformerQueue.isProcessing,
));
syncController.requestSync(
account: account,
filesController: controller,
personsController: personsController,
personProvider: accountPrefController.personProvider.value,
);
_tryStartMetadataTask();
}
@ -403,6 +411,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
final AccountPrefController accountPrefController;
final CollectionsController collectionsController;
final SessionController sessionController;
final SyncController syncController;
final PersonsController personsController;
final _itemTransformerQueue =
ComputeQueue<_ItemTransformerArgument, _ItemTransformerResult>();

View file

@ -18,8 +18,10 @@ import 'package:nc_photos/controller/account_controller.dart';
import 'package:nc_photos/controller/account_pref_controller.dart';
import 'package:nc_photos/controller/collections_controller.dart';
import 'package:nc_photos/controller/files_controller.dart';
import 'package:nc_photos/controller/persons_controller.dart';
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/controller/session_controller.dart';
import 'package:nc_photos/controller/sync_controller.dart';
import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/download_handler.dart';
@ -87,6 +89,8 @@ class HomePhotos2 extends StatelessWidget {
accountPrefController: accountController.accountPrefController,
collectionsController: accountController.collectionsController,
sessionController: accountController.sessionController,
syncController: accountController.syncController,
personsController: accountController.personsController,
),
child: const _WrappedHomePhotos(),
);

View file

@ -109,8 +109,12 @@ class _WrappedAccountSettingsState extends State<_WrappedAccountSettings>
if (_bloc.state.shouldResync &&
_bloc.state.personProvider != PersonProvider.none) {
_log.fine("[dispose] Requesting to resync account");
_accountController.syncController
.requestResync(_bloc.state.account, _bloc.state.personProvider);
_accountController.syncController.requestResync(
account: _bloc.state.account,
filesController: context.read(),
personsController: context.read(),
personProvider: _bloc.state.personProvider,
);
}
_animationController.dispose();
super.dispose();

View file

@ -49,6 +49,36 @@ class DbSyncResult {
final int update;
}
/// Sync results with ids
///
/// The meaning of the ids returned depends on the specific call
class DbSyncIdResult {
const DbSyncIdResult({
required this.insert,
required this.delete,
required this.update,
});
factory DbSyncIdResult.fromJson(JsonObj json) => DbSyncIdResult(
insert: (json["insert"] as List).cast<int>(),
delete: (json["delete"] as List).cast<int>(),
update: (json["update"] as List).cast<int>(),
);
JsonObj toJson() => {
"insert": insert,
"delete": delete,
"update": update,
};
bool get isEmpty => insert.isEmpty && delete.isEmpty && update.isEmpty;
bool get isNotEmpty => !isEmpty;
final List<int> insert;
final List<int> delete;
final List<int> update;
}
@toString
class DbLocationGroup with EquatableMixin {
const DbLocationGroup({
@ -235,7 +265,9 @@ abstract class NpDb {
});
/// Add or replace nc albums in db
Future<DbSyncResult> syncFavoriteFiles({
///
/// Return fileIds affected by this call
Future<DbSyncIdResult> syncFavoriteFiles({
required DbAccount account,
required List<int> favoriteFileIds,
});
@ -374,7 +406,7 @@ abstract class NpDb {
});
/// Replace all tags for [account]
Future<DbSyncResult> syncTags({
Future<DbSyncIdResult> syncTags({
required DbAccount account,
required List<DbTag> tags,
});

View file

@ -337,7 +337,7 @@ class NpDbSqlite implements NpDb {
}
@override
Future<DbSyncResult> syncFavoriteFiles({
Future<DbSyncIdResult> syncFavoriteFiles({
required DbAccount account,
required List<int> favoriteFileIds,
}) async {
@ -370,10 +370,10 @@ class NpDbSqlite implements NpDb {
isFavorite: const OrNull(false),
);
}
return DbSyncResult(
insert: inserts.length,
delete: deletes.length,
update: 0,
return DbSyncIdResult(
insert: inserts,
delete: deletes,
update: const [],
);
});
}
@ -834,7 +834,7 @@ class NpDbSqlite implements NpDb {
}
@override
Future<DbSyncResult> syncTags({
Future<DbSyncIdResult> syncTags({
required DbAccount account,
required List<DbTag> tags,
}) async {
@ -863,10 +863,10 @@ class NpDbSqlite implements NpDb {
updates: updates,
);
}
return DbSyncResult(
insert: inserts.length,
delete: deletes.length,
update: updates.length,
return DbSyncIdResult(
insert: inserts.map((e) => e.id).toList(),
delete: deletes.map((e) => e.id).toList(),
update: updates.map((e) => e.id).toList(),
);
});
}