Cache server version for use when server is down

This commit is contained in:
Ming Ming 2024-11-23 23:01:14 +08:00
parent d363d544d6
commit 2374fe83df
9 changed files with 109 additions and 20 deletions

View file

@ -63,6 +63,7 @@ class AccountController {
ServerController get serverController => ServerController get serverController =>
_serverController ??= ServerController( _serverController ??= ServerController(
account: _account!, account: _account!,
accountPrefController: accountPrefController,
); );
AccountPrefController get accountPrefController => AccountPrefController get accountPrefController =>

View file

@ -1,8 +1,12 @@
import 'dart:convert';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.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/entity/pref.dart';
import 'package:nc_photos/entity/server_status.dart';
import 'package:np_codegen/np_codegen.dart'; import 'package:np_codegen/np_codegen.dart';
import 'package:np_common/object_util.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
part 'account_pref_controller.g.dart'; part 'account_pref_controller.g.dart';
@ -52,6 +56,13 @@ class AccountPrefController {
value: value, value: value,
); );
Future<void> setServerStatus(ServerStatus value) => _set(
controller: _serverStatusController,
setter: (pref, value) =>
pref.setServerStatus(jsonEncode(value!.toJson())),
value: value,
);
Future<void> _set<T>({ Future<void> _set<T>({
required BehaviorSubject<T> controller, required BehaviorSubject<T> controller,
required Future<bool> Function(AccountPref pref, T value) setter, required Future<bool> Function(AccountPref pref, T value) setter,
@ -89,4 +100,8 @@ class AccountPrefController {
@npSubjectAccessor @npSubjectAccessor
late final _hasNewSharedAlbumController = late final _hasNewSharedAlbumController =
BehaviorSubject.seeded(_accountPref.hasNewSharedAlbum() ?? false); BehaviorSubject.seeded(_accountPref.hasNewSharedAlbum() ?? false);
@npSubjectAccessor
late final _serverStatusController = BehaviorSubject.seeded(_accountPref
.getServerStatus()
?.let((e) => ServerStatus.fromJson(jsonDecode(e))));
} }

View file

@ -50,4 +50,10 @@ extension $AccountPrefControllerNpSubjectAccessor on AccountPrefController {
Stream<bool> get hasNewSharedAlbumChange => Stream<bool> get hasNewSharedAlbumChange =>
hasNewSharedAlbum.distinct().skip(1); hasNewSharedAlbum.distinct().skip(1);
bool get hasNewSharedAlbumValue => _hasNewSharedAlbumController.value; bool get hasNewSharedAlbumValue => _hasNewSharedAlbumController.value;
// _serverStatusController
ValueStream<ServerStatus?> get serverStatus => _serverStatusController.stream;
Stream<ServerStatus?> get serverStatusNew => serverStatus.skip(1);
Stream<ServerStatus?> get serverStatusChange =>
serverStatus.distinct().skip(1);
ServerStatus? get serverStatusValue => _serverStatusController.value;
} }

View file

@ -5,4 +5,13 @@ extension on AccountPref {
provider.getBool(AccountPrefKey.hasNewSharedAlbum); provider.getBool(AccountPrefKey.hasNewSharedAlbum);
// Future<bool> setNewSharedAlbum(bool value) => // Future<bool> setNewSharedAlbum(bool value) =>
// provider.setBool(AccountPrefKey.hasNewSharedAlbum, value); // provider.setBool(AccountPrefKey.hasNewSharedAlbum, value);
String? getServerStatus() => provider.getString(AccountPrefKey.serverStatus);
Future<bool> setServerStatus(String? value) {
if (value == null) {
return provider.remove(AccountPrefKey.serverStatus);
} else {
return provider.setString(AccountPrefKey.serverStatus, value);
}
}
} }

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart'; import 'package:nc_photos/account.dart';
import 'package:nc_photos/api/entity_converter.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/entity/server_status.dart';
import 'package:nc_photos/np_api_util.dart'; import 'package:nc_photos/np_api_util.dart';
import 'package:np_api/np_api.dart' as api; import 'package:np_api/np_api.dart' as api;
@ -20,6 +21,7 @@ enum ServerFeature {
class ServerController { class ServerController {
ServerController({ ServerController({
required this.account, required this.account,
required this.accountPrefController,
}); });
void dispose() { void dispose() {
@ -33,17 +35,6 @@ class ServerController {
return _statusStreamContorller.stream; 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<void> _load() => _getStatus(); Future<void> _load() => _getStatus();
Future<void> _getStatus() async { Future<void> _getStatus() async {
@ -51,19 +42,49 @@ class ServerController {
final response = await ApiUtil.fromAccount(account).status().get(); final response = await ApiUtil.fromAccount(account).status().get();
if (!response.isGood) { if (!response.isGood) {
_log.severe("[_getStatus] Failed requesting server: $response"); _log.severe("[_getStatus] Failed requesting server: $response");
_loadStatus();
return; return;
} }
final apiStatus = await api.StatusParser().parse(response.body); final apiStatus = await api.StatusParser().parse(response.body);
final status = ApiStatusConverter.fromApi(apiStatus); final status = ApiStatusConverter.fromApi(apiStatus);
_log.info("[_getStatus] Server status: $status"); _log.info("[_getStatus] Server status: $status");
_statusStreamContorller.add(status); _statusStreamContorller.add(status);
_saveStatus(status);
} catch (e, stackTrace) { } catch (e, stackTrace) {
_log.severe("[_getStatus] Failed while get", e, stackTrace); _log.severe("[_getStatus] Failed while get", e, stackTrace);
_loadStatus();
return; 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 Account account;
final AccountPrefController accountPrefController;
final _statusStreamContorller = BehaviorSubject<ServerStatus>(); final _statusStreamContorller = BehaviorSubject<ServerStatus>();
} }
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;
}
}
}

View file

@ -230,6 +230,7 @@ enum AccountPrefKey implements PrefKeyInterface {
accountLabel, accountLabel,
lastNewCollectionType, lastNewCollectionType,
personProvider, personProvider,
serverStatus,
; ;
@override @override
@ -249,6 +250,8 @@ enum AccountPrefKey implements PrefKeyInterface {
return "lastNewCollectionType"; return "lastNewCollectionType";
case AccountPrefKey.personProvider: case AccountPrefKey.personProvider:
return "personProvider"; return "personProvider";
case AccountPrefKey.serverStatus:
return "serverStatus";
} }
} }
} }

View file

@ -1,9 +1,11 @@
import 'package:equatable/equatable.dart';
import 'package:np_common/type.dart';
import 'package:to_string/to_string.dart'; import 'package:to_string/to_string.dart';
part 'server_status.g.dart'; part 'server_status.g.dart';
@toString @toString
class ServerStatus { class ServerStatus with EquatableMixin {
const ServerStatus({ const ServerStatus({
required this.versionRaw, required this.versionRaw,
required this.versionName, required this.versionName,
@ -13,6 +15,25 @@ class ServerStatus {
@override @override
String toString() => _$toString(); 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<Object?> get props => [versionRaw, versionName, productName];
final String versionRaw; final String versionRaw;
final String versionName; final String versionName;
final String productName; final String productName;

View file

@ -9,10 +9,10 @@ import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.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/app_localizations.dart'; 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/controller/pref_controller.dart';
import 'package:nc_photos/di_container.dart'; import 'package:nc_photos/di_container.dart';
import 'package:nc_photos/entity/file_descriptor.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/event/native_event.dart';
import 'package:nc_photos/language_util.dart' as language_util; import 'package:nc_photos/language_util.dart' as language_util;
import 'package:nc_photos/use_case/battery_ensurer.dart'; import 'package:nc_photos/use_case/battery_ensurer.dart';
@ -95,13 +95,14 @@ class _Service {
} }
Future<void> _doWork() async { Future<void> _doWork() async {
final prefController = PrefController(Pref()); final c = KiwiContainer().resolve<DiContainer>();
final prefController = PrefController(c.pref);
final account = prefController.currentAccountValue; final account = prefController.currentAccountValue;
if (account == null) { if (account == null) {
_log.shout("[_doWork] account == null"); _log.shout("[_doWork] account == null");
return; return;
} }
final c = KiwiContainer().resolve<DiContainer>(); final accountPrefController = AccountPrefController(account: account);
final wifiEnsurer = WifiEnsurer( final wifiEnsurer = WifiEnsurer(
interrupter: _shouldRun.stream, interrupter: _shouldRun.stream,
@ -143,7 +144,7 @@ class _Service {
batteryEnsurer: batteryEnsurer, batteryEnsurer: batteryEnsurer,
); );
final processedIds = <int>[]; final processedIds = <int>[];
await for (final f in syncOp.syncAccount(account)) { await for (final f in syncOp.syncAccount(account, accountPrefController)) {
processedIds.add(f.fdId); processedIds.add(f.fdId);
service.setNotificationInfo( service.setNotificationInfo(
title: _L10n.global().metadataTaskProcessingNotification, title: _L10n.global().metadataTaskProcessingNotification,

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:nc_photos/account.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/controller/server_controller.dart';
import 'package:nc_photos/db/entity_converter.dart'; import 'package:nc_photos/db/entity_converter.dart';
import 'package:nc_photos/entity/exif_util.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/repo.dart';
import 'package:nc_photos/entity/file_util.dart' as file_util; import 'package:nc_photos/entity/file_util.dart' as file_util;
import 'package:nc_photos/geocoder_util.dart'; 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/battery_ensurer.dart';
import 'package:nc_photos/use_case/get_file_binary.dart'; import 'package:nc_photos/use_case/get_file_binary.dart';
import 'package:nc_photos/use_case/load_metadata.dart'; import 'package:nc_photos/use_case/load_metadata.dart';
@ -38,10 +40,14 @@ class SyncMetadata {
required this.batteryEnsurer, required this.batteryEnsurer,
}); });
Stream<File> syncAccount(Account account) async* { Stream<File> syncAccount(
Account account,
AccountPrefController accountPrefController,
) async* {
final bool isNcMetadataSupported; final bool isNcMetadataSupported;
try { try {
isNcMetadataSupported = await _isNcMetadataSupported(account); isNcMetadataSupported =
(await _isNcMetadataSupported(account, accountPrefController))!;
} catch (e) { } catch (e) {
_log.severe("[syncAccount] Failed to get server version", e); _log.severe("[syncAccount] Failed to get server version", e);
return; return;
@ -111,8 +117,14 @@ class SyncMetadata {
yield* stream; yield* stream;
} }
Future<bool> _isNcMetadataSupported(Account account) async { Future<bool?> _isNcMetadataSupported(
final serverController = ServerController(account: account); Account account,
AccountPrefController accountPrefController,
) async {
final serverController = ServerController(
account: account,
accountPrefController: accountPrefController,
);
await serverController.status.first.timeout(const Duration(seconds: 15)); await serverController.status.first.timeout(const Duration(seconds: 15));
return serverController.isSupported(ServerFeature.ncMetadata); return serverController.isSupported(ServerFeature.ncMetadata);
} }