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 { static const _tag = 'ServerSync'; @override ServerSyncState build() => const ServerSyncState(); Future 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 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 _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.new, );