From 2374fe83dfacf4e965e6f22a6f04ba959afefd0f Mon Sep 17 00:00:00 2001 From: Ming Ming Date: Sat, 23 Nov 2024 23:01:14 +0800 Subject: [PATCH] Cache server version for use when server is down --- app/lib/controller/account_controller.dart | 1 + .../controller/account_pref_controller.dart | 15 +++++++ .../controller/account_pref_controller.g.dart | 6 +++ .../account_pref_controller/util.dart | 9 ++++ app/lib/controller/server_controller.dart | 43 ++++++++++++++----- app/lib/entity/pref.dart | 3 ++ app/lib/entity/server_status.dart | 23 +++++++++- app/lib/service/service.dart | 9 ++-- .../use_case/sync_metadata/sync_metadata.dart | 20 +++++++-- 9 files changed, 109 insertions(+), 20 deletions(-) diff --git a/app/lib/controller/account_controller.dart b/app/lib/controller/account_controller.dart index 5541b16b..91436c43 100644 --- a/app/lib/controller/account_controller.dart +++ b/app/lib/controller/account_controller.dart @@ -63,6 +63,7 @@ class AccountController { ServerController get serverController => _serverController ??= ServerController( account: _account!, + accountPrefController: accountPrefController, ); AccountPrefController get accountPrefController => diff --git a/app/lib/controller/account_pref_controller.dart b/app/lib/controller/account_pref_controller.dart index ffde9669..a49ff13b 100644 --- a/app/lib/controller/account_pref_controller.dart +++ b/app/lib/controller/account_pref_controller.dart @@ -1,8 +1,12 @@ +import 'dart:convert'; + import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/entity/person.dart'; import 'package:nc_photos/entity/pref.dart'; +import 'package:nc_photos/entity/server_status.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_common/object_util.dart'; import 'package:rxdart/rxdart.dart'; part 'account_pref_controller.g.dart'; @@ -52,6 +56,13 @@ class AccountPrefController { value: value, ); + Future setServerStatus(ServerStatus value) => _set( + controller: _serverStatusController, + setter: (pref, value) => + pref.setServerStatus(jsonEncode(value!.toJson())), + value: value, + ); + Future _set({ required BehaviorSubject controller, required Future Function(AccountPref pref, T value) setter, @@ -89,4 +100,8 @@ class AccountPrefController { @npSubjectAccessor late final _hasNewSharedAlbumController = BehaviorSubject.seeded(_accountPref.hasNewSharedAlbum() ?? false); + @npSubjectAccessor + late final _serverStatusController = BehaviorSubject.seeded(_accountPref + .getServerStatus() + ?.let((e) => ServerStatus.fromJson(jsonDecode(e)))); } diff --git a/app/lib/controller/account_pref_controller.g.dart b/app/lib/controller/account_pref_controller.g.dart index fff374da..d2497a26 100644 --- a/app/lib/controller/account_pref_controller.g.dart +++ b/app/lib/controller/account_pref_controller.g.dart @@ -50,4 +50,10 @@ extension $AccountPrefControllerNpSubjectAccessor on AccountPrefController { Stream get hasNewSharedAlbumChange => hasNewSharedAlbum.distinct().skip(1); bool get hasNewSharedAlbumValue => _hasNewSharedAlbumController.value; +// _serverStatusController + ValueStream get serverStatus => _serverStatusController.stream; + Stream get serverStatusNew => serverStatus.skip(1); + Stream get serverStatusChange => + serverStatus.distinct().skip(1); + ServerStatus? get serverStatusValue => _serverStatusController.value; } diff --git a/app/lib/controller/account_pref_controller/util.dart b/app/lib/controller/account_pref_controller/util.dart index 48b368a8..b6a936b0 100644 --- a/app/lib/controller/account_pref_controller/util.dart +++ b/app/lib/controller/account_pref_controller/util.dart @@ -5,4 +5,13 @@ extension on AccountPref { provider.getBool(AccountPrefKey.hasNewSharedAlbum); // Future setNewSharedAlbum(bool value) => // provider.setBool(AccountPrefKey.hasNewSharedAlbum, value); + + String? getServerStatus() => provider.getString(AccountPrefKey.serverStatus); + Future setServerStatus(String? value) { + if (value == null) { + return provider.remove(AccountPrefKey.serverStatus); + } else { + return provider.setString(AccountPrefKey.serverStatus, value); + } + } } diff --git a/app/lib/controller/server_controller.dart b/app/lib/controller/server_controller.dart index 6e0164fc..7c3e49fa 100644 --- a/app/lib/controller/server_controller.dart +++ b/app/lib/controller/server_controller.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; import 'package:nc_photos/api/entity_converter.dart'; +import 'package:nc_photos/controller/account_pref_controller.dart'; import 'package:nc_photos/entity/server_status.dart'; import 'package:nc_photos/np_api_util.dart'; import 'package:np_api/np_api.dart' as api; @@ -20,6 +21,7 @@ enum ServerFeature { class ServerController { ServerController({ required this.account, + required this.accountPrefController, }); void dispose() { @@ -33,17 +35,6 @@ class ServerController { return _statusStreamContorller.stream; } - bool isSupported(ServerFeature feature) { - switch (feature) { - case ServerFeature.ncAlbum: - return !_statusStreamContorller.hasValue || - _statusStreamContorller.value.majorVersion >= 25; - case ServerFeature.ncMetadata: - return !_statusStreamContorller.hasValue || - _statusStreamContorller.value.majorVersion >= 28; - } - } - Future _load() => _getStatus(); Future _getStatus() async { @@ -51,19 +42,49 @@ class ServerController { final response = await ApiUtil.fromAccount(account).status().get(); if (!response.isGood) { _log.severe("[_getStatus] Failed requesting server: $response"); + _loadStatus(); return; } final apiStatus = await api.StatusParser().parse(response.body); final status = ApiStatusConverter.fromApi(apiStatus); _log.info("[_getStatus] Server status: $status"); _statusStreamContorller.add(status); + _saveStatus(status); } catch (e, stackTrace) { _log.severe("[_getStatus] Failed while get", e, stackTrace); + _loadStatus(); return; } } + void _loadStatus() { + final cache = accountPrefController.serverStatusValue; + if (cache != null) { + _statusStreamContorller.add(cache); + } + } + + void _saveStatus(ServerStatus status) { + final cache = accountPrefController.serverStatusValue; + if (cache != status) { + accountPrefController.setServerStatus(status); + } + } + final Account account; + final AccountPrefController accountPrefController; final _statusStreamContorller = BehaviorSubject(); } + +extension ServerControllerExtension on ServerController { + bool isSupported(ServerFeature feature) { + final status = _statusStreamContorller.valueOrNull; + switch (feature) { + case ServerFeature.ncAlbum: + return status == null || status.majorVersion >= 25; + case ServerFeature.ncMetadata: + return status != null && status.majorVersion >= 28; + } + } +} diff --git a/app/lib/entity/pref.dart b/app/lib/entity/pref.dart index 5cfb6a8b..4cac6ea6 100644 --- a/app/lib/entity/pref.dart +++ b/app/lib/entity/pref.dart @@ -230,6 +230,7 @@ enum AccountPrefKey implements PrefKeyInterface { accountLabel, lastNewCollectionType, personProvider, + serverStatus, ; @override @@ -249,6 +250,8 @@ enum AccountPrefKey implements PrefKeyInterface { return "lastNewCollectionType"; case AccountPrefKey.personProvider: return "personProvider"; + case AccountPrefKey.serverStatus: + return "serverStatus"; } } } diff --git a/app/lib/entity/server_status.dart b/app/lib/entity/server_status.dart index 8e27ed81..94ac5b17 100644 --- a/app/lib/entity/server_status.dart +++ b/app/lib/entity/server_status.dart @@ -1,9 +1,11 @@ +import 'package:equatable/equatable.dart'; +import 'package:np_common/type.dart'; import 'package:to_string/to_string.dart'; part 'server_status.g.dart'; @toString -class ServerStatus { +class ServerStatus with EquatableMixin { const ServerStatus({ required this.versionRaw, required this.versionName, @@ -13,6 +15,25 @@ class ServerStatus { @override String toString() => _$toString(); + factory ServerStatus.fromJson(JsonObj json) { + return ServerStatus( + versionRaw: json["versionRaw"], + versionName: json["versionName"], + productName: json["productName"], + ); + } + + JsonObj toJson() { + return { + "versionRaw": versionRaw, + "versionName": versionName, + "productName": productName, + }; + } + + @override + List get props => [versionRaw, versionName, productName]; + final String versionRaw; final String versionName; final String productName; diff --git a/app/lib/service/service.dart b/app/lib/service/service.dart index cf689d87..b06da03f 100644 --- a/app/lib/service/service.dart +++ b/app/lib/service/service.dart @@ -9,10 +9,10 @@ import 'package:kiwi/kiwi.dart'; import 'package:logging/logging.dart'; import 'package:nc_photos/app_init.dart' as app_init; import 'package:nc_photos/app_localizations.dart'; +import 'package:nc_photos/controller/account_pref_controller.dart'; import 'package:nc_photos/controller/pref_controller.dart'; import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; -import 'package:nc_photos/entity/pref.dart'; import 'package:nc_photos/event/native_event.dart'; import 'package:nc_photos/language_util.dart' as language_util; import 'package:nc_photos/use_case/battery_ensurer.dart'; @@ -95,13 +95,14 @@ class _Service { } Future _doWork() async { - final prefController = PrefController(Pref()); + final c = KiwiContainer().resolve(); + final prefController = PrefController(c.pref); final account = prefController.currentAccountValue; if (account == null) { _log.shout("[_doWork] account == null"); return; } - final c = KiwiContainer().resolve(); + final accountPrefController = AccountPrefController(account: account); final wifiEnsurer = WifiEnsurer( interrupter: _shouldRun.stream, @@ -143,7 +144,7 @@ class _Service { batteryEnsurer: batteryEnsurer, ); final processedIds = []; - await for (final f in syncOp.syncAccount(account)) { + await for (final f in syncOp.syncAccount(account, accountPrefController)) { processedIds.add(f.fdId); service.setNotificationInfo( title: _L10n.global().metadataTaskProcessingNotification, diff --git a/app/lib/use_case/sync_metadata/sync_metadata.dart b/app/lib/use_case/sync_metadata/sync_metadata.dart index 6b5eccc7..fa1f53ec 100644 --- a/app/lib/use_case/sync_metadata/sync_metadata.dart +++ b/app/lib/use_case/sync_metadata/sync_metadata.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:logging/logging.dart'; import 'package:nc_photos/account.dart'; +import 'package:nc_photos/controller/account_pref_controller.dart'; import 'package:nc_photos/controller/server_controller.dart'; import 'package:nc_photos/db/entity_converter.dart'; import 'package:nc_photos/entity/exif_util.dart'; @@ -10,6 +11,7 @@ import 'package:nc_photos/entity/file/file_cache_manager.dart'; import 'package:nc_photos/entity/file/repo.dart'; import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/geocoder_util.dart'; +import 'package:nc_photos/service/service.dart'; import 'package:nc_photos/use_case/battery_ensurer.dart'; import 'package:nc_photos/use_case/get_file_binary.dart'; import 'package:nc_photos/use_case/load_metadata.dart'; @@ -38,10 +40,14 @@ class SyncMetadata { required this.batteryEnsurer, }); - Stream syncAccount(Account account) async* { + Stream syncAccount( + Account account, + AccountPrefController accountPrefController, + ) async* { final bool isNcMetadataSupported; try { - isNcMetadataSupported = await _isNcMetadataSupported(account); + isNcMetadataSupported = + (await _isNcMetadataSupported(account, accountPrefController))!; } catch (e) { _log.severe("[syncAccount] Failed to get server version", e); return; @@ -111,8 +117,14 @@ class SyncMetadata { yield* stream; } - Future _isNcMetadataSupported(Account account) async { - final serverController = ServerController(account: account); + Future _isNcMetadataSupported( + Account account, + AccountPrefController accountPrefController, + ) async { + final serverController = ServerController( + account: account, + accountPrefController: accountPrefController, + ); await serverController.status.first.timeout(const Duration(seconds: 15)); return serverController.isSupported(ServerFeature.ncMetadata); }