From f66345cded8a0b66e90c99e32c4508292ff356d7 Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sat, 22 Jul 2023 22:26:51 +0800 Subject: [PATCH] Resync with server after modifying person provider --- app/lib/controller/account_controller.dart | 8 +++ .../controller/account_pref_controller.dart | 1 + app/lib/controller/sync_controller.dart | 51 +++++++++++++++++ app/lib/use_case/person/sync_person.dart | 5 +- app/lib/use_case/startup_sync.dart | 57 ++++++++++--------- app/lib/widget/home_photos.dart | 33 +---------- app/lib/widget/settings/account/bloc.dart | 5 +- .../widget/settings/account/state_event.dart | 3 + app/lib/widget/settings/account_settings.dart | 22 ++++++- .../widget/settings/account_settings.g.dart | 11 ++-- 10 files changed, 128 insertions(+), 68 deletions(-) create mode 100644 app/lib/controller/sync_controller.dart diff --git a/app/lib/controller/account_controller.dart b/app/lib/controller/account_controller.dart index 14c8dd44..5b7c9d4f 100644 --- a/app/lib/controller/account_controller.dart +++ b/app/lib/controller/account_controller.dart @@ -4,6 +4,7 @@ import 'package:nc_photos/controller/account_pref_controller.dart'; import 'package:nc_photos/controller/collections_controller.dart'; import 'package:nc_photos/controller/persons_controller.dart'; import 'package:nc_photos/controller/server_controller.dart'; +import 'package:nc_photos/controller/sync_controller.dart'; import 'package:nc_photos/di_container.dart'; class AccountController { @@ -17,6 +18,8 @@ class AccountController { _accountPrefController = null; _personsController?.dispose(); _personsController = null; + _syncController?.dispose(); + _syncController = null; } Account get account => _account!; @@ -45,9 +48,14 @@ class AccountController { accountPrefController: accountPrefController, ); + SyncController get syncController => _syncController ??= SyncController( + account: _account!, + ); + Account? _account; CollectionsController? _collectionsController; ServerController? _serverController; AccountPrefController? _accountPrefController; PersonsController? _personsController; + SyncController? _syncController; } diff --git a/app/lib/controller/account_pref_controller.dart b/app/lib/controller/account_pref_controller.dart index 0fb2bfdf..da113f7f 100644 --- a/app/lib/controller/account_pref_controller.dart +++ b/app/lib/controller/account_pref_controller.dart @@ -16,6 +16,7 @@ class AccountPrefController { void dispose() { _shareFolderController.close(); _accountLabelController.close(); + _personProviderController.close(); } ValueStream get shareFolder => _shareFolderController.stream; diff --git a/app/lib/controller/sync_controller.dart b/app/lib/controller/sync_controller.dart new file mode 100644 index 00000000..a394ac7d --- /dev/null +++ b/app/lib/controller/sync_controller.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:nc_photos/account.dart'; +import 'package:nc_photos/entity/person.dart'; +import 'package:nc_photos/use_case/startup_sync.dart'; + +class SyncController { + SyncController({ + required this.account, + this.onPeopleUpdated, + }); + + void dispose() { + _isDisposed = true; + } + + Future requestSync( + Account account, PersonProvider personProvider) async { + if (_isDisposed) { + return; + } + if (_syncCompleter == null) { + _syncCompleter = Completer(); + final result = await StartupSync.runInIsolate(account, personProvider); + if (!_isDisposed && result.isSyncPersonUpdated) { + onPeopleUpdated?.call(); + } + _syncCompleter!.complete(); + } else { + return _syncCompleter!.future; + } + } + + Future requestResync( + Account account, PersonProvider personProvider) async { + if (_syncCompleter?.isCompleted == true) { + _syncCompleter = null; + return requestSync(account, personProvider); + } else { + // already syncing + return requestSync(account, personProvider); + } + } + + final Account account; + final VoidCallback? onPeopleUpdated; + + Completer? _syncCompleter; + var _isDisposed = false; +} diff --git a/app/lib/use_case/person/sync_person.dart b/app/lib/use_case/person/sync_person.dart index 7005a1e1..369ade7b 100644 --- a/app/lib/use_case/person/sync_person.dart +++ b/app/lib/use_case/person/sync_person.dart @@ -4,7 +4,6 @@ import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/person.dart'; -import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/use_case/face_recognition_person/sync_face_recognition_person.dart'; import 'package:nc_photos/use_case/recognize_face/sync_recognize_face.dart'; import 'package:np_codegen/np_codegen.dart'; @@ -18,9 +17,7 @@ class SyncPerson { /// Sync people in cache db with remote server /// /// Return if any people were updated - Future call(Account account, AccountPref accountPref) async { - final provider = - PersonProvider.fromValue(accountPref.getPersonProviderOr()); + Future call(Account account, PersonProvider provider) async { _log.info("[call] Current provider: $provider"); switch (provider) { case PersonProvider.none: diff --git a/app/lib/use_case/startup_sync.dart b/app/lib/use_case/startup_sync.dart index 5eedfd40..034f05be 100644 --- a/app/lib/use_case/startup_sync.dart +++ b/app/lib/use_case/startup_sync.dart @@ -4,11 +4,11 @@ import 'package:event_bus/event_bus.dart'; import 'package:flutter_isolate/flutter_isolate.dart'; import 'package:kiwi/kiwi.dart'; 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/di_container.dart'; -import 'package:nc_photos/entity/pref.dart'; -import 'package:nc_photos/entity/pref/provider/memory.dart'; +import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/use_case/person/sync_person.dart'; @@ -29,25 +29,28 @@ class StartupSync { /// Sync in a background isolate static Future runInIsolate( - Account account, AccountPref accountPref) async { - if (platform_k.isWeb) { - // not supported on web - final c = KiwiContainer().resolve(); - return await StartupSync(c)(account, accountPref); - } else { - // we can't use regular isolate here because self-signed cert support - // requires native plugins - final resultJson = await flutterCompute( - _isolateMain, await _IsolateMessage(account, accountPref).toJson()); - 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); - return result; - } + Account account, PersonProvider personProvider) async { + return _mutex.protect(() async { + if (platform_k.isWeb) { + // not supported on web + final c = KiwiContainer().resolve(); + return await StartupSync(c)(account, personProvider); + } else { + // we can't use regular isolate here because self-signed cert support + // requires native plugins + final resultJson = await flutterCompute( + _isolateMain, _IsolateMessage(account, personProvider).toJson()); + 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); + return result; + } + }); } - Future call(Account account, AccountPref accountPref) async { + Future call( + Account account, PersonProvider personProvider) async { _log.info("[_run] Begin sync"); final stopwatch = Stopwatch()..start(); late final int syncFavoriteCount; @@ -64,7 +67,7 @@ class StartupSync { _log.shout("[_run] Failed while SyncTag", e, stackTrace); } try { - isSyncPersonUpdated = await SyncPerson(_c)(account, accountPref); + isSyncPersonUpdated = await SyncPerson(_c)(account, personProvider); } catch (e, stackTrace) { _log.shout("[_run] Failed while SyncPerson", e, stackTrace); } @@ -83,6 +86,8 @@ class StartupSync { } final DiContainer _c; + + static final _mutex = Mutex(); } class SyncResult { @@ -106,23 +111,23 @@ class SyncResult { } class _IsolateMessage { - const _IsolateMessage(this.account, this.accountPref); + const _IsolateMessage(this.account, this.personProvider); factory _IsolateMessage.fromJson(JsonObj json) => _IsolateMessage( Account.fromJson( json["account"].cast(), upgraderV1: const AccountUpgraderV1(), )!, - AccountPref.scoped(PrefMemoryProvider.fromJson(json["accountPref"])), + PersonProvider.fromValue(json["personProvider"]), ); - Future toJson() async => { + JsonObj toJson() => { "account": account.toJson(), - "accountPref": await accountPref.toJson(), + "personProvider": personProvider.index, }; final Account account; - final AccountPref accountPref; + final PersonProvider personProvider; } @pragma("vm:entry-point") @@ -131,6 +136,6 @@ Future _isolateMain(JsonObj messageJson) async { await app_init.init(app_init.InitIsolateType.flutterIsolate); final c = KiwiContainer().resolve(); - final result = await StartupSync(c)(message.account, message.accountPref); + final result = await StartupSync(c)(message.account, message.personProvider); return result.toJson(); } diff --git a/app/lib/widget/home_photos.dart b/app/lib/widget/home_photos.dart index 06583973..6cb2a681 100644 --- a/app/lib/widget/home_photos.dart +++ b/app/lib/widget/home_photos.dart @@ -37,7 +37,6 @@ import 'package:nc_photos/share_handler.dart'; import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/throttler.dart'; -import 'package:nc_photos/use_case/startup_sync.dart'; import 'package:nc_photos/widget/builder/photo_list_item_builder.dart'; import 'package:nc_photos/widget/collection_browser.dart'; import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart'; @@ -557,20 +556,6 @@ class _HomePhotosState extends State } } - Future _startupSync() async { - if (!_hasResyncedFavorites.value) { - _hasResyncedFavorites.value = true; - final result = await StartupSync.runInIsolate( - widget.account, _accountPrefController.raw); - if (mounted) { - if (result.isSyncPersonUpdated) { - unawaited( - context.read().personsController.reload()); - } - } - } - } - /// Transform a File list to grid items void _transformItems( List files, { @@ -611,7 +596,8 @@ class _HomePhotosState extends State if (isPostSuccess) { _isScrollbarVisible = true; - _startupSync(); + context.read().syncController.requestSync( + widget.account, _accountPrefController.personProvider.value); _tryStartMetadataTask(); } }); @@ -720,21 +706,6 @@ class _HomePhotosState extends State } } - Primitive get _hasResyncedFavorites { - final name = bloc_util.getInstNameForRootAwareAccount( - "HomePhotosState._hasResyncedFavorites", widget.account); - try { - _log.fine("[_hasResyncedFavorites] Resolving for '$name'"); - return KiwiContainer().resolve>(name); - } catch (_) { - _log.info( - "[_hasResyncedFavorites] New instance for account: ${widget.account}"); - final obj = Primitive(false); - KiwiContainer().registerInstance>(obj, name: name); - return obj; - } - } - late final _bloc = ScanAccountDirBloc.of(widget.account); late final _queryProgressBloc = ProgressBloc(); late final _accountPrefController = diff --git a/app/lib/widget/settings/account/bloc.dart b/app/lib/widget/settings/account/bloc.dart index a8101ffc..5b207900 100644 --- a/app/lib/widget/settings/account/bloc.dart +++ b/app/lib/widget/settings/account/bloc.dart @@ -139,7 +139,10 @@ class _Bloc extends Bloc<_Event, _State> { void _onOnUpdatePersonProvider( _OnUpdatePersonProvider ev, Emitter<_State> emit) { _log.info(ev); - emit(state.copyWith(personProvider: ev.personProvider)); + emit(state.copyWith( + personProvider: ev.personProvider, + shouldResync: true, + )); } void _onSetError(_SetError ev, Emitter<_State> emit) { diff --git a/app/lib/widget/settings/account/state_event.dart b/app/lib/widget/settings/account/state_event.dart index 80ce8bbf..4c5fb6bc 100644 --- a/app/lib/widget/settings/account/state_event.dart +++ b/app/lib/widget/settings/account/state_event.dart @@ -9,6 +9,7 @@ class _State { required this.label, required this.shareFolder, required this.personProvider, + required this.shouldResync, this.error, }); @@ -24,6 +25,7 @@ class _State { label: label, shareFolder: shareFolder, personProvider: personProvider, + shouldResync: false, ); } @@ -35,6 +37,7 @@ class _State { final String? label; final String shareFolder; final PersonProvider personProvider; + final bool shouldResync; final ExceptionEvent? error; } diff --git a/app/lib/widget/settings/account_settings.dart b/app/lib/widget/settings/account_settings.dart index daf78e0e..bcb96693 100644 --- a/app/lib/widget/settings/account_settings.dart +++ b/app/lib/widget/settings/account_settings.dart @@ -87,12 +87,29 @@ class _WrappedAccountSettings extends StatefulWidget { const _WrappedAccountSettings(); @override - State createState() => _WrappedDeveloperSettingsState(); + State createState() => __WrappedAccountSettingsState(); } @npLog -class _WrappedDeveloperSettingsState extends State<_WrappedAccountSettings> +class __WrappedAccountSettingsState extends State<_WrappedAccountSettings> with RouteAware, PageVisibilityMixin { + @override + void initState() { + super.initState(); + _accountController = context.read(); + } + + @override + void dispose() { + 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); + } + super.dispose(); + } + @override Widget build(BuildContext context) { return Scaffold( @@ -245,6 +262,7 @@ class _WrappedDeveloperSettingsState extends State<_WrappedAccountSettings> } late final _bloc = context.read<_Bloc>(); + late final AccountController _accountController; } class _DoneButton extends StatelessWidget { diff --git a/app/lib/widget/settings/account_settings.g.dart b/app/lib/widget/settings/account_settings.g.dart index 36df3d20..04051eed 100644 --- a/app/lib/widget/settings/account_settings.g.dart +++ b/app/lib/widget/settings/account_settings.g.dart @@ -19,6 +19,7 @@ abstract class $_StateCopyWithWorker { String? label, String? shareFolder, PersonProvider? personProvider, + bool? shouldResync, ExceptionEvent? error}); } @@ -32,6 +33,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { dynamic label = copyWithNull, dynamic shareFolder, dynamic personProvider, + dynamic shouldResync, dynamic error = copyWithNull}) { return _State( shouldReload: shouldReload as bool? ?? that.shouldReload, @@ -40,6 +42,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker { shareFolder: shareFolder as String? ?? that.shareFolder, personProvider: personProvider as PersonProvider? ?? that.personProvider, + shouldResync: shouldResync as bool? ?? that.shouldResync, error: error == copyWithNull ? that.error : error as ExceptionEvent?); } @@ -55,13 +58,13 @@ extension $_StateCopyWith on _State { // NpLogGenerator // ************************************************************************** -extension _$_WrappedDeveloperSettingsStateNpLog - on _WrappedDeveloperSettingsState { +extension _$__WrappedAccountSettingsStateNpLog + on __WrappedAccountSettingsState { // ignore: unused_element Logger get _log => log; static final log = - Logger("widget.settings.account_settings._WrappedDeveloperSettingsState"); + Logger("widget.settings.account_settings.__WrappedAccountSettingsState"); } extension _$_PersonProviderDialogNpLog on _PersonProviderDialog { @@ -86,7 +89,7 @@ extension _$_BlocNpLog on _Bloc { extension _$_StateToString on _State { String _$toString() { // ignore: unnecessary_string_interpolations - return "_State {shouldReload: $shouldReload, account: $account, label: $label, shareFolder: $shareFolder, personProvider: ${personProvider.name}, error: $error}"; + return "_State {shouldReload: $shouldReload, account: $account, label: $label, shareFolder: $shareFolder, personProvider: ${personProvider.name}, shouldResync: $shouldResync, error: $error}"; } }