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/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;
}

View file

@ -16,6 +16,7 @@ class AccountPrefController {
void dispose() {
_shareFolderController.close();
_accountLabelController.close();
_personProviderController.close();
}
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/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<bool> call(Account account, AccountPref accountPref) async {
final provider =
PersonProvider.fromValue(accountPref.getPersonProviderOr());
Future<bool> call(Account account, PersonProvider provider) async {
_log.info("[call] Current provider: $provider");
switch (provider) {
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: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<SyncResult> runInIsolate(
Account account, AccountPref accountPref) async {
if (platform_k.isWeb) {
// not supported on web
final c = KiwiContainer().resolve<DiContainer>();
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<DiContainer>();
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<SyncResult> call(Account account, AccountPref accountPref) async {
Future<SyncResult> 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<String, dynamic>(),
upgraderV1: const AccountUpgraderV1(),
)!,
AccountPref.scoped(PrefMemoryProvider.fromJson(json["accountPref"])),
PersonProvider.fromValue(json["personProvider"]),
);
Future<JsonObj> toJson() async => {
JsonObj toJson() => <String, dynamic>{
"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<JsonObj> _isolateMain(JsonObj messageJson) async {
await app_init.init(app_init.InitIsolateType.flutterIsolate);
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();
}

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/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<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
void _transformItems(
List<FileDescriptor> files, {
@ -611,7 +596,8 @@ class _HomePhotosState extends State<HomePhotos>
if (isPostSuccess) {
_isScrollbarVisible = true;
_startupSync();
context.read<AccountController>().syncController.requestSync(
widget.account, _accountPrefController.personProvider.value);
_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 _queryProgressBloc = ProgressBloc();
late final _accountPrefController =

View file

@ -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) {

View file

@ -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;
}

View file

@ -87,12 +87,29 @@ class _WrappedAccountSettings extends StatefulWidget {
const _WrappedAccountSettings();
@override
State<StatefulWidget> createState() => _WrappedDeveloperSettingsState();
State<StatefulWidget> createState() => __WrappedAccountSettingsState();
}
@npLog
class _WrappedDeveloperSettingsState extends State<_WrappedAccountSettings>
class __WrappedAccountSettingsState extends State<_WrappedAccountSettings>
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
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 {

View file

@ -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}";
}
}