Resync with server after modifying person provider

This commit is contained in:
Ming Ming 2023-07-22 22:26:51 +08:00
parent 4e03c76d0d
commit f66345cded
10 changed files with 128 additions and 68 deletions

View file

@ -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/collections_controller.dart';
import 'package:nc_photos/controller/persons_controller.dart'; import 'package:nc_photos/controller/persons_controller.dart';
import 'package:nc_photos/controller/server_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'; import 'package:nc_photos/di_container.dart';
class AccountController { class AccountController {
@ -17,6 +18,8 @@ class AccountController {
_accountPrefController = null; _accountPrefController = null;
_personsController?.dispose(); _personsController?.dispose();
_personsController = null; _personsController = null;
_syncController?.dispose();
_syncController = null;
} }
Account get account => _account!; Account get account => _account!;
@ -45,9 +48,14 @@ class AccountController {
accountPrefController: accountPrefController, accountPrefController: accountPrefController,
); );
SyncController get syncController => _syncController ??= SyncController(
account: _account!,
);
Account? _account; Account? _account;
CollectionsController? _collectionsController; CollectionsController? _collectionsController;
ServerController? _serverController; ServerController? _serverController;
AccountPrefController? _accountPrefController; AccountPrefController? _accountPrefController;
PersonsController? _personsController; PersonsController? _personsController;
SyncController? _syncController;
} }

View file

@ -16,6 +16,7 @@ class AccountPrefController {
void dispose() { void dispose() {
_shareFolderController.close(); _shareFolderController.close();
_accountLabelController.close(); _accountLabelController.close();
_personProviderController.close();
} }
ValueStream<String> get shareFolder => _shareFolderController.stream; ValueStream<String> get shareFolder => _shareFolderController.stream;

View file

@ -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<void> 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<void> 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<void>? _syncCompleter;
var _isDisposed = false;
}

View file

@ -4,7 +4,6 @@ import 'package:logging/logging.dart';
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/person.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/face_recognition_person/sync_face_recognition_person.dart';
import 'package:nc_photos/use_case/recognize_face/sync_recognize_face.dart'; import 'package:nc_photos/use_case/recognize_face/sync_recognize_face.dart';
import 'package:np_codegen/np_codegen.dart'; import 'package:np_codegen/np_codegen.dart';
@ -18,9 +17,7 @@ class SyncPerson {
/// Sync people in cache db with remote server /// Sync people in cache db with remote server
/// ///
/// Return if any people were updated /// Return if any people were updated
Future<bool> call(Account account, AccountPref accountPref) async { Future<bool> call(Account account, PersonProvider provider) async {
final provider =
PersonProvider.fromValue(accountPref.getPersonProviderOr());
_log.info("[call] Current provider: $provider"); _log.info("[call] Current provider: $provider");
switch (provider) { switch (provider) {
case PersonProvider.none: case PersonProvider.none:

View file

@ -4,11 +4,11 @@ import 'package:event_bus/event_bus.dart';
import 'package:flutter_isolate/flutter_isolate.dart'; import 'package:flutter_isolate/flutter_isolate.dart';
import 'package:kiwi/kiwi.dart'; import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:mutex/mutex.dart';
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_init.dart' as app_init; import 'package:nc_photos/app_init.dart' as app_init;
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/person.dart';
import 'package:nc_photos/entity/pref/provider/memory.dart';
import 'package:nc_photos/event/event.dart'; import 'package:nc_photos/event/event.dart';
import 'package:nc_photos/platform/k.dart' as platform_k; import 'package:nc_photos/platform/k.dart' as platform_k;
import 'package:nc_photos/use_case/person/sync_person.dart'; import 'package:nc_photos/use_case/person/sync_person.dart';
@ -29,25 +29,28 @@ class StartupSync {
/// Sync in a background isolate /// Sync in a background isolate
static Future<SyncResult> runInIsolate( static Future<SyncResult> runInIsolate(
Account account, AccountPref accountPref) async { Account account, PersonProvider personProvider) async {
if (platform_k.isWeb) { return _mutex.protect(() async {
// not supported on web if (platform_k.isWeb) {
final c = KiwiContainer().resolve<DiContainer>(); // not supported on web
return await StartupSync(c)(account, accountPref); final c = KiwiContainer().resolve<DiContainer>();
} else { return await StartupSync(c)(account, personProvider);
// we can't use regular isolate here because self-signed cert support } else {
// requires native plugins // we can't use regular isolate here because self-signed cert support
final resultJson = await flutterCompute( // requires native plugins
_isolateMain, await _IsolateMessage(account, accountPref).toJson()); final resultJson = await flutterCompute(
final result = SyncResult.fromJson(resultJson); _isolateMain, _IsolateMessage(account, personProvider).toJson());
// events fired in background isolate won't be noticed by the main isolate, final result = SyncResult.fromJson(resultJson);
// so we fire them again here // events fired in background isolate won't be noticed by the main isolate,
_broadcastResult(account, result); // so we fire them again here
return result; _broadcastResult(account, result);
} return result;
}
});
} }
Future<SyncResult> call(Account account, AccountPref accountPref) async { Future<SyncResult> call(
Account account, PersonProvider personProvider) async {
_log.info("[_run] Begin sync"); _log.info("[_run] Begin sync");
final stopwatch = Stopwatch()..start(); final stopwatch = Stopwatch()..start();
late final int syncFavoriteCount; late final int syncFavoriteCount;
@ -64,7 +67,7 @@ class StartupSync {
_log.shout("[_run] Failed while SyncTag", e, stackTrace); _log.shout("[_run] Failed while SyncTag", e, stackTrace);
} }
try { try {
isSyncPersonUpdated = await SyncPerson(_c)(account, accountPref); isSyncPersonUpdated = await SyncPerson(_c)(account, personProvider);
} catch (e, stackTrace) { } catch (e, stackTrace) {
_log.shout("[_run] Failed while SyncPerson", e, stackTrace); _log.shout("[_run] Failed while SyncPerson", e, stackTrace);
} }
@ -83,6 +86,8 @@ class StartupSync {
} }
final DiContainer _c; final DiContainer _c;
static final _mutex = Mutex();
} }
class SyncResult { class SyncResult {
@ -106,23 +111,23 @@ class SyncResult {
} }
class _IsolateMessage { class _IsolateMessage {
const _IsolateMessage(this.account, this.accountPref); const _IsolateMessage(this.account, this.personProvider);
factory _IsolateMessage.fromJson(JsonObj json) => _IsolateMessage( factory _IsolateMessage.fromJson(JsonObj json) => _IsolateMessage(
Account.fromJson( Account.fromJson(
json["account"].cast<String, dynamic>(), json["account"].cast<String, dynamic>(),
upgraderV1: const AccountUpgraderV1(), upgraderV1: const AccountUpgraderV1(),
)!, )!,
AccountPref.scoped(PrefMemoryProvider.fromJson(json["accountPref"])), PersonProvider.fromValue(json["personProvider"]),
); );
Future<JsonObj> toJson() async => { JsonObj toJson() => <String, dynamic>{
"account": account.toJson(), "account": account.toJson(),
"accountPref": await accountPref.toJson(), "personProvider": personProvider.index,
}; };
final Account account; final Account account;
final AccountPref accountPref; final PersonProvider personProvider;
} }
@pragma("vm:entry-point") @pragma("vm:entry-point")
@ -131,6 +136,6 @@ Future<JsonObj> _isolateMain(JsonObj messageJson) async {
await app_init.init(app_init.InitIsolateType.flutterIsolate); await app_init.init(app_init.InitIsolateType.flutterIsolate);
final c = KiwiContainer().resolve<DiContainer>(); final c = KiwiContainer().resolve<DiContainer>();
final result = await StartupSync(c)(message.account, message.accountPref); final result = await StartupSync(c)(message.account, message.personProvider);
return result.toJson(); return result.toJson();
} }

View file

@ -37,7 +37,6 @@ import 'package:nc_photos/share_handler.dart';
import 'package:nc_photos/snack_bar_manager.dart'; import 'package:nc_photos/snack_bar_manager.dart';
import 'package:nc_photos/theme.dart'; import 'package:nc_photos/theme.dart';
import 'package:nc_photos/throttler.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/builder/photo_list_item_builder.dart';
import 'package:nc_photos/widget/collection_browser.dart'; import 'package:nc_photos/widget/collection_browser.dart';
import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart'; import 'package:nc_photos/widget/handler/add_selection_to_collection_handler.dart';
@ -557,20 +556,6 @@ class _HomePhotosState extends State<HomePhotos>
} }
} }
Future<void> _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<AccountController>().personsController.reload());
}
}
}
}
/// Transform a File list to grid items /// Transform a File list to grid items
void _transformItems( void _transformItems(
List<FileDescriptor> files, { List<FileDescriptor> files, {
@ -611,7 +596,8 @@ class _HomePhotosState extends State<HomePhotos>
if (isPostSuccess) { if (isPostSuccess) {
_isScrollbarVisible = true; _isScrollbarVisible = true;
_startupSync(); context.read<AccountController>().syncController.requestSync(
widget.account, _accountPrefController.personProvider.value);
_tryStartMetadataTask(); _tryStartMetadataTask();
} }
}); });
@ -720,21 +706,6 @@ class _HomePhotosState extends State<HomePhotos>
} }
} }
Primitive<bool> get _hasResyncedFavorites {
final name = bloc_util.getInstNameForRootAwareAccount(
"HomePhotosState._hasResyncedFavorites", widget.account);
try {
_log.fine("[_hasResyncedFavorites] Resolving for '$name'");
return KiwiContainer().resolve<Primitive<bool>>(name);
} catch (_) {
_log.info(
"[_hasResyncedFavorites] New instance for account: ${widget.account}");
final obj = Primitive(false);
KiwiContainer().registerInstance<Primitive<bool>>(obj, name: name);
return obj;
}
}
late final _bloc = ScanAccountDirBloc.of(widget.account); late final _bloc = ScanAccountDirBloc.of(widget.account);
late final _queryProgressBloc = ProgressBloc(); late final _queryProgressBloc = ProgressBloc();
late final _accountPrefController = late final _accountPrefController =

View file

@ -139,7 +139,10 @@ class _Bloc extends Bloc<_Event, _State> {
void _onOnUpdatePersonProvider( void _onOnUpdatePersonProvider(
_OnUpdatePersonProvider ev, Emitter<_State> emit) { _OnUpdatePersonProvider ev, Emitter<_State> emit) {
_log.info(ev); _log.info(ev);
emit(state.copyWith(personProvider: ev.personProvider)); emit(state.copyWith(
personProvider: ev.personProvider,
shouldResync: true,
));
} }
void _onSetError(_SetError ev, Emitter<_State> emit) { void _onSetError(_SetError ev, Emitter<_State> emit) {

View file

@ -9,6 +9,7 @@ class _State {
required this.label, required this.label,
required this.shareFolder, required this.shareFolder,
required this.personProvider, required this.personProvider,
required this.shouldResync,
this.error, this.error,
}); });
@ -24,6 +25,7 @@ class _State {
label: label, label: label,
shareFolder: shareFolder, shareFolder: shareFolder,
personProvider: personProvider, personProvider: personProvider,
shouldResync: false,
); );
} }
@ -35,6 +37,7 @@ class _State {
final String? label; final String? label;
final String shareFolder; final String shareFolder;
final PersonProvider personProvider; final PersonProvider personProvider;
final bool shouldResync;
final ExceptionEvent? error; final ExceptionEvent? error;
} }

View file

@ -87,12 +87,29 @@ class _WrappedAccountSettings extends StatefulWidget {
const _WrappedAccountSettings(); const _WrappedAccountSettings();
@override @override
State<StatefulWidget> createState() => _WrappedDeveloperSettingsState(); State<StatefulWidget> createState() => __WrappedAccountSettingsState();
} }
@npLog @npLog
class _WrappedDeveloperSettingsState extends State<_WrappedAccountSettings> class __WrappedAccountSettingsState extends State<_WrappedAccountSettings>
with RouteAware, PageVisibilityMixin { with RouteAware, PageVisibilityMixin {
@override
void initState() {
super.initState();
_accountController = context.read<AccountController>();
}
@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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -245,6 +262,7 @@ class _WrappedDeveloperSettingsState extends State<_WrappedAccountSettings>
} }
late final _bloc = context.read<_Bloc>(); late final _bloc = context.read<_Bloc>();
late final AccountController _accountController;
} }
class _DoneButton extends StatelessWidget { class _DoneButton extends StatelessWidget {

View file

@ -19,6 +19,7 @@ abstract class $_StateCopyWithWorker {
String? label, String? label,
String? shareFolder, String? shareFolder,
PersonProvider? personProvider, PersonProvider? personProvider,
bool? shouldResync,
ExceptionEvent? error}); ExceptionEvent? error});
} }
@ -32,6 +33,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
dynamic label = copyWithNull, dynamic label = copyWithNull,
dynamic shareFolder, dynamic shareFolder,
dynamic personProvider, dynamic personProvider,
dynamic shouldResync,
dynamic error = copyWithNull}) { dynamic error = copyWithNull}) {
return _State( return _State(
shouldReload: shouldReload as bool? ?? that.shouldReload, shouldReload: shouldReload as bool? ?? that.shouldReload,
@ -40,6 +42,7 @@ class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
shareFolder: shareFolder as String? ?? that.shareFolder, shareFolder: shareFolder as String? ?? that.shareFolder,
personProvider: personProvider:
personProvider as PersonProvider? ?? that.personProvider, personProvider as PersonProvider? ?? that.personProvider,
shouldResync: shouldResync as bool? ?? that.shouldResync,
error: error == copyWithNull ? that.error : error as ExceptionEvent?); error: error == copyWithNull ? that.error : error as ExceptionEvent?);
} }
@ -55,13 +58,13 @@ extension $_StateCopyWith on _State {
// NpLogGenerator // NpLogGenerator
// ************************************************************************** // **************************************************************************
extension _$_WrappedDeveloperSettingsStateNpLog extension _$__WrappedAccountSettingsStateNpLog
on _WrappedDeveloperSettingsState { on __WrappedAccountSettingsState {
// ignore: unused_element // ignore: unused_element
Logger get _log => log; Logger get _log => log;
static final log = static final log =
Logger("widget.settings.account_settings._WrappedDeveloperSettingsState"); Logger("widget.settings.account_settings.__WrappedAccountSettingsState");
} }
extension _$_PersonProviderDialogNpLog on _PersonProviderDialog { extension _$_PersonProviderDialogNpLog on _PersonProviderDialog {
@ -86,7 +89,7 @@ extension _$_BlocNpLog on _Bloc {
extension _$_StateToString on _State { extension _$_StateToString on _State {
String _$toString() { String _$toString() {
// ignore: unnecessary_string_interpolations // 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}";
} }
} }