Initial commit
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../core/domain/errors.dart';
|
||||
import '../shared/utils/app_logger.dart';
|
||||
import 'danmaku_settings_provider.dart';
|
||||
import 'di_providers.dart';
|
||||
import 'server_provider.dart';
|
||||
import 'session_provider.dart';
|
||||
import 'sm_account_provider.dart';
|
||||
|
||||
|
||||
enum ServerSyncStatus { success, info, failure, needsLogin, needsVip }
|
||||
|
||||
|
||||
class ServerSyncOutcome {
|
||||
final ServerSyncStatus status;
|
||||
final String message;
|
||||
|
||||
const ServerSyncOutcome(this.status, this.message);
|
||||
|
||||
bool get isError =>
|
||||
status == ServerSyncStatus.failure ||
|
||||
status == ServerSyncStatus.needsLogin ||
|
||||
status == ServerSyncStatus.needsVip;
|
||||
}
|
||||
|
||||
|
||||
class ServerSyncState {
|
||||
final bool busy;
|
||||
final DateTime? lastSyncAt;
|
||||
final String? lastSyncError;
|
||||
const ServerSyncState({
|
||||
this.busy = false,
|
||||
this.lastSyncAt,
|
||||
this.lastSyncError,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
class ServerSyncNotifier extends Notifier<ServerSyncState> {
|
||||
static const _tag = 'ServerSync';
|
||||
|
||||
@override
|
||||
ServerSyncState build() => const ServerSyncState();
|
||||
|
||||
|
||||
Future<ServerSyncOutcome> upload() async {
|
||||
if (state.busy) {
|
||||
return const ServerSyncOutcome(ServerSyncStatus.info, '正在同步,请稍候');
|
||||
}
|
||||
final access = await _resolveAccess();
|
||||
if (access.outcome != null) return access.outcome!;
|
||||
final creds = access.creds!;
|
||||
|
||||
state = const ServerSyncState(busy: true);
|
||||
try {
|
||||
final uc = await ref.read(uploadServerSyncUseCaseProvider.future);
|
||||
final r = await uc.execute(
|
||||
baseUrl: creds.baseUrl,
|
||||
accessToken: creds.accessToken,
|
||||
);
|
||||
final outcome = ServerSyncOutcome(
|
||||
ServerSyncStatus.success,
|
||||
'已上传 ${r.serverCount} 个服务器、${r.sessionCount} 个登录态、'
|
||||
'${r.danmakuSourceCount} 个弹幕源、'
|
||||
'${r.scriptWidgetCount} 个模块、${r.scriptWidgetSubscriptionCount} 个订阅到云端',
|
||||
);
|
||||
state = ServerSyncState(lastSyncAt: DateTime.now());
|
||||
return outcome;
|
||||
} on DomainError catch (e) {
|
||||
state = ServerSyncState(lastSyncError: '上传失败:${e.message}');
|
||||
return ServerSyncOutcome(ServerSyncStatus.failure, '上传失败:${e.message}');
|
||||
} catch (e) {
|
||||
AppLogger.warn(_tag, 'upload failed', e);
|
||||
state = const ServerSyncState(lastSyncError: '上传失败,请稍后重试');
|
||||
return const ServerSyncOutcome(ServerSyncStatus.failure, '上传失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<ServerSyncOutcome> pull() async {
|
||||
if (state.busy) {
|
||||
return const ServerSyncOutcome(ServerSyncStatus.info, '正在同步,请稍候');
|
||||
}
|
||||
final access = await _resolveAccess();
|
||||
if (access.outcome != null) return access.outcome!;
|
||||
final creds = access.creds!;
|
||||
|
||||
state = const ServerSyncState(busy: true);
|
||||
try {
|
||||
final uc = await ref.read(pullServerSyncUseCaseProvider.future);
|
||||
final r = await uc.execute(
|
||||
baseUrl: creds.baseUrl,
|
||||
accessToken: creds.accessToken,
|
||||
);
|
||||
if (r.empty) {
|
||||
return const ServerSyncOutcome(ServerSyncStatus.info, '云端还没有备份');
|
||||
}
|
||||
|
||||
await ref.read(serverListProvider.notifier).refresh();
|
||||
await ref.read(sessionProvider.notifier).refresh();
|
||||
ref.invalidate(danmakuSettingsProvider);
|
||||
ref.invalidate(installedScriptWidgetsProvider);
|
||||
ref.invalidate(scriptWidgetSubscriptionsProvider);
|
||||
|
||||
unawaited(_repairInBackground());
|
||||
|
||||
final outcome = ServerSyncOutcome(
|
||||
ServerSyncStatus.success,
|
||||
'已从云端恢复 ${r.serverCount} 个服务器、${r.sessionCount} 个登录态、'
|
||||
'${r.danmakuSourceCount} 个弹幕源、'
|
||||
'${r.scriptWidgetCount} 个模块、${r.scriptWidgetSubscriptionCount} 个订阅',
|
||||
);
|
||||
state = ServerSyncState(lastSyncAt: DateTime.now());
|
||||
return outcome;
|
||||
} on DomainError catch (e) {
|
||||
state = ServerSyncState(lastSyncError: '拉取失败:${e.message}');
|
||||
return ServerSyncOutcome(ServerSyncStatus.failure, '拉取失败:${e.message}');
|
||||
} catch (e) {
|
||||
AppLogger.warn(_tag, 'pull failed', e);
|
||||
state = const ServerSyncState(lastSyncError: '拉取失败,请稍后重试');
|
||||
return const ServerSyncOutcome(ServerSyncStatus.failure, '拉取失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _repairInBackground() async {
|
||||
try {
|
||||
final uc = await ref.read(repairSessionsUseCaseProvider.future);
|
||||
final repaired = await uc.execute();
|
||||
if (repaired > 0) {
|
||||
await ref.read(sessionProvider.notifier).refresh();
|
||||
AppLogger.info(_tag, 'repaired $repaired session(s) after pull');
|
||||
}
|
||||
} catch (e) {
|
||||
AppLogger.warn(_tag, 'background repair failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Future<({_SyncCredentials? creds, ServerSyncOutcome? outcome})>
|
||||
_resolveAccess() async {
|
||||
const needsLogin = (
|
||||
creds: null,
|
||||
outcome: ServerSyncOutcome(ServerSyncStatus.needsLogin, '请先登录 sm 账号后再同步'),
|
||||
);
|
||||
|
||||
var account = await ref.read(smAccountProvider.future);
|
||||
if (!account.canUseDataPlane) return needsLogin;
|
||||
|
||||
var token = await ref.read(smAccountProvider.notifier).getAccessToken();
|
||||
if (token == null || token.isEmpty) return needsLogin;
|
||||
account = await ref.read(smAccountProvider.future);
|
||||
|
||||
if (!account.isVip) {
|
||||
token = await ref
|
||||
.read(smAccountProvider.notifier)
|
||||
.getAccessToken(force: true);
|
||||
if (token == null || token.isEmpty) return needsLogin;
|
||||
account = await ref.read(smAccountProvider.future);
|
||||
}
|
||||
|
||||
if (!account.isVip) {
|
||||
return (
|
||||
creds: null,
|
||||
outcome: const ServerSyncOutcome(
|
||||
ServerSyncStatus.needsVip,
|
||||
'服务器云同步为 VIP 功能,请开通 VIP 后使用',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
creds: _SyncCredentials(baseUrl: account.baseUrl, accessToken: token),
|
||||
outcome: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SyncCredentials {
|
||||
final String baseUrl;
|
||||
final String accessToken;
|
||||
const _SyncCredentials({required this.baseUrl, required this.accessToken});
|
||||
}
|
||||
|
||||
final serverSyncProvider =
|
||||
NotifierProvider<ServerSyncNotifier, ServerSyncState>(
|
||||
ServerSyncNotifier.new,
|
||||
);
|
||||
Reference in New Issue
Block a user