import 'dart:async'; import 'package:ainoval/config/app_config.dart'; import 'package:ainoval/services/api_service/base/api_client.dart'; import 'package:ainoval/services/api_service/base/api_exception.dart'; import 'package:ainoval/services/local_storage_service.dart'; import 'package:ainoval/utils/logger.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; /// 数据同步服务 /// /// 负责在本地数据和远程API之间同步数据,支持离线模式和冲突解决 class SyncService { SyncService({ required this.apiService, required this.localStorageService, }); final ApiClient apiService; final LocalStorageService localStorageService; // 同步状态流 final _syncStateController = StreamController.broadcast(); Stream get syncStateStream => _syncStateController.stream; // 当前同步状态 SyncState _currentState = SyncState.idle(); SyncState get currentState => _currentState; // 网络连接监听器 StreamSubscription? _connectivitySubscription; // 自动同步定时器 Timer? _autoSyncTimer; // 服务是否已关闭 bool _isDisposed = false; bool get isDisposed => _isDisposed; /// 初始化同步服务 Future init() async { AppLogger.i('SyncService', '初始化同步服务'); // 监听网络连接状态 _connectivitySubscription = Connectivity().onConnectivityChanged.listen((result) { final isOnline = result != ConnectivityResult.none; AppLogger.d('SyncService', '网络连接状态变化: ${isOnline ? "在线" : "离线"}'); _handleConnectivityChange(isOnline); }); // 检查当前网络状态 final connectivityResult = await Connectivity().checkConnectivity(); final isOnline = connectivityResult != ConnectivityResult.none; AppLogger.d('SyncService', '当前网络状态: ${isOnline ? "在线" : "离线"}'); _updateSyncState(isOnline: isOnline); // 设置自动同步定时器 _setupAutoSync(); } /// 设置自动同步 void _setupAutoSync() { AppLogger.i('SyncService', '设置自动同步定时器,每5分钟同步一次'); _autoSyncTimer?.cancel(); _autoSyncTimer = Timer.periodic(const Duration(minutes: 5), (_) async { if (_currentState.isOnline) { // 检查当前小说ID是否设置 final currentNovelId = await localStorageService.getCurrentNovelId(); if (currentNovelId == null) { AppLogger.w('SyncService', '自动同步触发,但无当前小说ID,跳过'); return; } AppLogger.d('SyncService', '自动同步触发,当前小说ID: $currentNovelId'); syncAll(); } }); } /// 处理网络连接变化 void _handleConnectivityChange(bool isOnline) { // Store the previous online state before updating final wasOffline = !_currentState.isOnline; _updateSyncState(isOnline: isOnline); // Trigger sync only when coming back online if (isOnline && wasOffline) { // 检查当前小说ID是否设置后再同步 localStorageService.getCurrentNovelId().then((currentNovelId) { if (currentNovelId != null) { AppLogger.i('SyncService', '网络恢复,开始同步数据,当前小说ID: $currentNovelId'); syncAll(); // syncAll will now also handle pending messages } else { AppLogger.w('SyncService', '网络恢复,但无当前小说ID,不执行自动同步'); } }); } } /// 更新同步状态 void _updateSyncState({ bool? isOnline, bool? isSyncing, String? error, double? progress, }) { // 如果服务已关闭,则不更新状态 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,忽略状态更新'); return; } _currentState = SyncState( isOnline: isOnline ?? _currentState.isOnline, isSyncing: isSyncing ?? _currentState.isSyncing, error: error, progress: progress ?? _currentState.progress, ); _syncStateController.add(_currentState); AppLogger.v('SyncService', '同步状态更新: $_currentState'); } /// 同步所有数据 Future syncAll() async { // 如果服务已关闭,直接返回 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,无法执行同步'); return false; } if (_currentState.isSyncing) { AppLogger.w('SyncService', '同步已在进行中,跳过本次同步'); return false; } if (!_currentState.isOnline) { AppLogger.w('SyncService', '无网络连接,无法同步'); _updateSyncState(error: '无网络连接,无法同步'); return false; } try { AppLogger.i('SyncService', '开始全量数据同步'); _updateSyncState( isSyncing: true, progress: 0.0, error: null); // Clear previous error // --- Sync Pending Messages First --- AppLogger.d('SyncService', '开始同步待发送消息'); await _syncPendingMessages(); _updateSyncState(progress: 0.1); // Adjust progress steps // --- Sync other data types --- AppLogger.d('SyncService', '开始同步小说数据'); await _syncNovels(); _updateSyncState(progress: 0.3); AppLogger.d('SyncService', '开始同步场景内容'); await _syncScenes(); _updateSyncState(progress: 0.5); AppLogger.d('SyncService', '开始同步编辑器内容'); await _syncEditorContents(); _updateSyncState(progress: 0.7); // Sync Chat Session METADATA (title, etc.) AppLogger.d('SyncService', '开始同步聊天会话元数据'); await _syncChatSessions(); // This now only syncs metadata _updateSyncState(progress: 0.9); // Example progress // --- Final step, maybe sync user profile or other settings --- _updateSyncState(progress: 1.0); // Example finish AppLogger.i('SyncService', '全量数据同步完成'); _updateSyncState(isSyncing: false, error: null); // Clear error on success return true; } catch (e, stackTrace) { AppLogger.e('SyncService', '同步失败', e, stackTrace); // Preserve isOnline state, set error _updateSyncState(isSyncing: false, error: '同步失败: $e'); return false; } } /// 同步小说数据 Future _syncNovels() async { try { // 获取当前正在编辑的小说ID final currentNovelId = await localStorageService.getCurrentNovelId(); if (currentNovelId == null) { AppLogger.w('SyncService', '无当前小说ID,跳过小说同步'); return; } final syncList = await localStorageService.getSyncList('novel'); AppLogger.d('SyncService', '需要同步的小说数量: ${syncList.length}'); // 筛选出当前小说 final novelIdsToSync = syncList.where((novelId) => novelId == currentNovelId).toList(); AppLogger.d('SyncService', '当前小说需要同步: ${novelIdsToSync.length} (当前小说ID: $currentNovelId)'); if (novelIdsToSync.isEmpty) { AppLogger.i('SyncService', '当前小说不需要同步,跳过'); return; } for (final novelId in novelIdsToSync) { final localNovel = await localStorageService.getNovel(novelId); if (localNovel == null) { AppLogger.w('SyncService', '本地小说不存在: $novelId'); continue; } AppLogger.i('SyncService', '同步小说: ${localNovel.title}($novelId)'); // 构建后端所需的小说数据结构 final backendNovelJson = { 'id': localNovel.id, 'title': localNovel.title, 'coverImage': localNovel.coverUrl, // 确保包含作者信息 'author': localNovel.author?.toJson() ?? { 'id': AppConfig.userId ?? '', 'username': AppConfig.username ?? 'user' }, 'lastEditedChapterId': localNovel.lastEditedChapterId, 'createdAt': localNovel.createdAt.toIso8601String(), 'updatedAt': DateTime.now().toIso8601String(), 'structure': { 'acts': localNovel.acts .map((act) => { 'id': act.id, 'title': act.title, 'order': act.order, 'chapters': act.chapters .map((chapter) => { 'id': chapter.id, 'title': chapter.title, 'order': chapter.order, // 注意:章节中只需包含ID,场景内容通过scenesByChapter单独提供 'sceneIds': chapter.scenes.map((scene) => scene.id).toList(), }) .toList(), }) .toList(), }, }; // 组织场景数据,按章节分组 Map>> scenesByChapter = {}; for (final act in localNovel.acts) { for (final chapter in act.chapters) { if (chapter.scenes.isNotEmpty) { scenesByChapter[chapter.id] = chapter.scenes .map((scene) => { 'id': scene.id, 'novelId': localNovel.id, 'chapterId': chapter.id, 'content': scene.content, 'summary': scene.summary.content, 'updatedAt': scene.lastEdited.toIso8601String(), 'version': scene.version, 'title': '', 'sequence': 0, 'sceneType': 'NORMAL', }) .toList(); } } } // 组装完整的请求数据 final novelWithScenesJson = { 'novel': backendNovelJson, 'scenesByChapter': scenesByChapter, }; // 调用updateNovelWithScenes接口 await apiService.updateNovelWithScenes(novelWithScenesJson); await localStorageService.clearSyncFlagByType('novel', novelId); AppLogger.d('SyncService', '小说同步完成: $novelId'); } } catch (e, stackTrace) { AppLogger.e('SyncService', '同步小说数据失败', e, stackTrace); throw SyncException('同步小说数据失败: $e'); } } /// 同步场景内容 Future _syncScenes() async { try { // 获取当前正在编辑的小说ID final currentNovelId = await localStorageService.getCurrentNovelId(); if (currentNovelId == null) { AppLogger.w('SyncService', '无当前小说ID,跳过场景同步'); return; } final syncList = await localStorageService.getSyncList('scene'); AppLogger.d('SyncService', '需要同步的场景数量: ${syncList.length}'); // 筛选出当前小说的场景 final scenesToSync = syncList.where((sceneKey) { final parts = sceneKey.split('_'); return parts.length == 4 && parts[0] == currentNovelId; }).toList(); AppLogger.d('SyncService', '当前小说的场景需要同步: ${scenesToSync.length} (当前小说ID: $currentNovelId)'); if (scenesToSync.isEmpty) { AppLogger.i('SyncService', '当前小说没有场景需要同步,跳过'); return; } for (final sceneKey in scenesToSync) { final parts = sceneKey.split('_'); if (parts.length != 4) { AppLogger.w('SyncService', '无效的场景键格式: $sceneKey'); continue; } final novelId = parts[0]; final actId = parts[1]; final chapterId = parts[2]; final sceneId = parts[3]; final localScene = await localStorageService.getSceneContent( novelId, actId, chapterId, sceneId); if (localScene == null) { AppLogger.w('SyncService', '本地场景不存在: $sceneKey'); continue; } AppLogger.i('SyncService', '同步场景: $sceneKey'); final sceneData = localScene.toJson(); await apiService.updateScene(sceneData); await localStorageService.clearSyncFlagByType('scene', sceneKey); AppLogger.d('SyncService', '场景同步完成: $sceneKey'); } } catch (e, stackTrace) { AppLogger.e('SyncService', '同步场景内容失败', e, stackTrace); throw SyncException('同步场景内容失败: $e'); } } /// 同步编辑器内容 Future _syncEditorContents() async { try { // 获取当前正在编辑的小说ID final currentNovelId = await localStorageService.getCurrentNovelId(); if (currentNovelId == null) { AppLogger.w('SyncService', '无当前小说ID,跳过编辑器内容同步'); return; } final syncList = await localStorageService.getSyncList('editor'); AppLogger.d('SyncService', '需要同步的编辑器内容数量: ${syncList.length}'); // 筛选出当前小说的编辑器内容 final contentsToSync = syncList.where((contentKey) { final parts = contentKey.split('_'); return parts.length >= 2 && parts[0] == currentNovelId; }).toList(); AppLogger.d('SyncService', '当前小说的编辑器内容需要同步: ${contentsToSync.length} (当前小说ID: $currentNovelId)'); if (contentsToSync.isEmpty) { AppLogger.i('SyncService', '当前小说没有编辑器内容需要同步,跳过'); return; } for (final contentKey in contentsToSync) { final parts = contentKey.split('_'); if (parts.length < 2) { AppLogger.w('SyncService', '无效的编辑器内容键格式: $contentKey'); continue; } final novelId = parts[0]; final chapterId = parts[1]; final localContent = await localStorageService.getEditorContent(novelId, chapterId, ''); if (localContent == null) { AppLogger.w('SyncService', '本地编辑器内容不存在: $contentKey'); continue; } AppLogger.i('SyncService', '同步编辑器内容: $contentKey'); await apiService.saveEditorContent( novelId, chapterId, localContent.toJson()); await localStorageService.clearSyncFlagByType('editor', contentKey); AppLogger.d('SyncService', '编辑器内容同步完成: $contentKey'); } } catch (e, stackTrace) { AppLogger.e('SyncService', '同步编辑器内容失败', e, stackTrace); throw SyncException('同步编辑器内容失败: $e'); } } /// 同步聊天会话元数据 (No longer sends full message history) Future _syncChatSessions() async { // This method now only syncs session metadata like title, updatedAt try { // 获取当前正在编辑的小说ID final currentNovelId = await localStorageService.getCurrentNovelId(); if (currentNovelId == null) { AppLogger.w('SyncService', '无当前小说ID,跳过聊天会话同步'); return; } final sessions = await localStorageService.getSessionsToSync(); AppLogger.d('SyncService', '需要同步的聊天会话元数据数量: ${sessions.length}'); // 筛选出当前小说的聊天会话 // 注意:这里假设 ChatSession 模型有 novelId 属性,如果没有,需要调整过滤逻辑 final sessionsToSync = sessions.where((session) => session.metadata != null && session.metadata!['novelId'] == currentNovelId).toList(); AppLogger.d('SyncService', '当前小说的聊天会话需要同步: ${sessionsToSync.length} (当前小说ID: $currentNovelId)'); if (sessionsToSync.isEmpty) { AppLogger.i('SyncService', '当前小说没有聊天会话需要同步,跳过'); return; } // No need for userId here if updateSession API only updates metadata // If updateSession *requires* userId, get it once: // final String currentUserId = await _getCurrentUserId(); for (final session in sessionsToSync) { AppLogger.i('SyncService', '同步聊天会话元数据: ${session.id}'); // Construct updates payload - only include fields managed locally // that need syncing (like title if user can rename offline) final Map updates = { 'title': session.title, // Include lastUpdatedAt from local session to inform server? // 'updatedAt': session.lastUpdatedAt.toIso8601String(), // Or maybe server handles updatedAt automatically on update? }; // Only call update if there are actual updates to send if (updates.isNotEmpty) { // If updateSession requires userId, pass it: userId: currentUserId, await apiService.updateAiChatSession( userId: await _getCurrentUserId(), // Get userId if needed by API sessionId: session.id, updates: updates, ); } // Else: Session might be marked for sync without local changes, skip API call? // REMOVED: Loop sending messages using getMessagesForSession await localStorageService.clearSyncFlagByType( 'chat_session', session.id); AppLogger.d('SyncService', '聊天会话元数据同步完成: ${session.id}'); } } catch (e, stackTrace) { // Don't throw - allow other sync tasks to proceed if possible AppLogger.e('SyncService', '同步聊天会话元数据失败', e, stackTrace); // Optionally update state with a non-fatal error? // _updateSyncState(error: '部分同步失败: 聊天会话元数据'); // Be careful not to overwrite fatal errors } } /// --- New Method: Sync Pending Chat Messages --- Future _syncPendingMessages() async { try { // 如果服务已关闭,直接返回 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,无法同步待发送消息'); return; } // 获取当前正在编辑的小说ID final currentNovelId = await localStorageService.getCurrentNovelId(); if (currentNovelId == null) { AppLogger.w('SyncService', '无当前小说ID,跳过待发送消息同步'); return; } final pendingMessages = await localStorageService.getPendingMessages(); if (pendingMessages.isEmpty) { AppLogger.d('SyncService', '没有待发送的消息。'); return; } // 筛选出当前小说的待发送消息 final messagesToSync = pendingMessages.where((message) { // 检查消息元数据中是否包含小说ID final metadata = message['metadata'] as Map?; return metadata != null && metadata['novelId'] == currentNovelId; }).toList(); if (messagesToSync.isEmpty) { AppLogger.i('SyncService', '当前小说没有待发送消息需要同步,跳过'); return; } AppLogger.i('SyncService', '开始处理 ${messagesToSync.length} 条当前小说的待发送消息。 (当前小说ID: $currentNovelId)'); final String currentUserId = await _getCurrentUserId(); // Get User ID once for (final messageData in messagesToSync) { final localId = messageData['localId'] as String?; final sessionId = messageData['sessionId'] as String?; final content = messageData['content'] as String?; final metadata = messageData['metadata'] as Map?; // Important: Use the userId stored with the message if available, // otherwise use currentUserId. This handles cases where sync might // happen after user logout/login, though ideally pending messages // should be cleared on logout. final userIdToSend = messageData['userId'] as String? ?? currentUserId; if (localId == null || sessionId == null || content == null) { AppLogger.e('SyncService', '待发送消息数据不完整,跳过: $messageData'); // Optionally remove corrupted data // if (localId != null) await localStorageService.removePendingMessage(localId); continue; } try { AppLogger.d( 'SyncService', '尝试发送消息: localId=$localId, sessionId=$sessionId'); // Call the actual API to send the message await apiService.sendAiChatMessage( userId: userIdToSend, sessionId: sessionId, content: content, metadata: metadata, ); // If sendMessage succeeds, remove from local pending queue await localStorageService.removePendingMessage(localId); AppLogger.i('SyncService', '成功发送并移除待发送消息: localId=$localId'); // OPTIONAL: Add the successfully sent message to the local history cache // This requires constructing a proper ChatMessage object from the response // or assuming success and creating one locally. This is complex. // It might be simpler to rely on fetching history later. } on ApiException catch (apiError, stack) { // Catch specific API errors AppLogger.e( 'SyncService', '发送待处理消息失败 (API Error $localId): ${apiError.message}', apiError, stack); // Decide if error is temporary or permanent. // For now, leave in queue and retry later. // If 4xx error, maybe remove from queue? if (apiError.statusCode >= 400 && apiError.statusCode < 500 && apiError.statusCode != 401 && apiError.statusCode != 429) { AppLogger.w('SyncService', '接收到客户端错误 (${apiError.statusCode}),可能移除待发送消息 $localId'); // Consider removing permanently failed message // await localStorageService.removePendingMessage(localId); } } catch (e, stackTrace) { // Catch other errors AppLogger.e( 'SyncService', '发送待处理消息时发生未知错误 ($localId)', e, stackTrace); // Leave in queue for retry } } AppLogger.i('SyncService', '处理待发送消息完成。'); } catch (e, stackTrace) { // Error fetching or processing the queue itself AppLogger.e('SyncService', '处理待发送消息队列时出错', e, stackTrace); // Don't throw, allow other sync tasks. Update state? // _updateSyncState(error: '部分同步失败: 待发送消息'); } } /// 同步单个小说 Future syncNovel(String novelId) async { // 如果服务已关闭,直接返回 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,无法同步小说'); return false; } if (!_currentState.isOnline) { _updateSyncState(error: '无网络连接,无法同步'); return false; } try { // 获取本地小说 final localNovel = await localStorageService.getNovel(novelId); if (localNovel == null) return false; // 构建后端所需的小说数据结构 final backendNovelJson = { 'id': localNovel.id, 'title': localNovel.title, 'coverImage': localNovel.coverUrl, // 确保包含作者信息 'author': localNovel.author?.toJson() ?? { 'id': AppConfig.userId ?? '', 'username': AppConfig.username ?? 'user' }, 'lastEditedChapterId': localNovel.lastEditedChapterId, 'createdAt': localNovel.createdAt.toIso8601String(), 'updatedAt': DateTime.now().toIso8601String(), 'structure': { 'acts': localNovel.acts .map((act) => { 'id': act.id, 'title': act.title, 'order': act.order, 'chapters': act.chapters .map((chapter) => { 'id': chapter.id, 'title': chapter.title, 'order': chapter.order, // 注意:章节中只需包含ID,场景内容通过scenesByChapter单独提供 'sceneIds': chapter.scenes.map((scene) => scene.id).toList(), }) .toList(), }) .toList(), }, }; // 组织场景数据,按章节分组 Map>> scenesByChapter = {}; for (final act in localNovel.acts) { for (final chapter in act.chapters) { if (chapter.scenes.isNotEmpty) { scenesByChapter[chapter.id] = chapter.scenes .map((scene) => { 'id': scene.id, 'novelId': localNovel.id, 'chapterId': chapter.id, 'content': scene.content, 'summary': scene.summary.content, 'updatedAt': scene.lastEdited.toIso8601String(), 'version': scene.version, 'title': '', 'sequence': 0, 'sceneType': 'NORMAL', }) .toList(); } } } // 组装完整的请求数据 final novelWithScenesJson = { 'novel': backendNovelJson, 'scenesByChapter': scenesByChapter, }; // 调用updateNovelWithScenes接口 await apiService.updateNovelWithScenes(novelWithScenesJson); // 标记为已同步 await localStorageService.clearSyncFlagByType('novel', novelId); return true; } catch (e) { AppLogger.e('Services/sync_service', '同步小说失败', e); _updateSyncState(error: '同步小说失败: $e'); return false; } } /// 同步单个场景 Future syncScene( String novelId, String actId, String chapterId, String sceneId) async { // 如果服务已关闭,直接返回 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,无法同步场景'); return false; } if (!_currentState.isOnline) { _updateSyncState(error: '无网络连接,无法同步'); return false; } try { // 获取本地场景 final localScene = await localStorageService.getSceneContent( novelId, actId, chapterId, sceneId); if (localScene == null) return false; // 上传到服务器 final sceneData = localScene.toJson(); await apiService.updateScene(sceneData); // 标记为已同步 final sceneKey = '${novelId}_${actId}_${chapterId}_$sceneId'; await localStorageService.clearSyncFlagByType('scene', sceneKey); return true; } catch (e) { AppLogger.e('Services/sync_service', '同步场景失败', e); _updateSyncState(error: '同步场景失败: $e'); return false; } } /// 同步单个编辑器内容 Future syncEditorContent(String novelId, String chapterId, String sceneId) async { // 如果服务已关闭,直接返回 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,无法同步编辑器内容'); return false; } if (!_currentState.isOnline) { _updateSyncState(error: '无网络连接,无法同步'); return false; } try { // 获取本地编辑器内容 final localContent = await localStorageService.getEditorContent( novelId, chapterId, ''); // 传递空的 sceneId 或适配 if (localContent == null) return false; // 上传到服务器 await apiService.saveEditorContent( novelId, chapterId, localContent.toJson()); // 标记为已同步 final contentKey = '${novelId}_$chapterId'; // 调整 key await localStorageService.clearSyncFlagByType('editor', contentKey); return true; } catch (e) { AppLogger.e('Services/sync_service', '同步编辑器内容失败', e); _updateSyncState(error: '同步编辑器内容失败: $e'); return false; } } /// 同步单个聊天会话元数据 (不再发送消息历史) Future syncChatSession(String sessionId) async { // 如果服务已关闭,直接返回 if (_isDisposed) { AppLogger.w('SyncService', '服务已关闭,无法同步聊天会话'); return false; } if (!_currentState.isOnline) { _updateSyncState(error: '无网络连接,无法同步'); return false; } try { final session = await localStorageService.getChatSession(sessionId); if (session == null) { AppLogger.w('SyncService', '尝试同步单个会话元数据,但本地未找到: $sessionId'); await localStorageService.clearSyncFlagByType( 'chat_session', sessionId); // 清除无效标记 return false; // 无法同步不存在的会话 } // 同步元数据 (例如: title) final Map updates = {'title': session.title}; if (updates.isNotEmpty) { await apiService.updateAiChatSession( userId: await _getCurrentUserId(), // 如果 API 需要,获取 userId sessionId: session.id, updates: updates, ); AppLogger.d('SyncService', '单个聊天会话元数据 API 更新调用完成: $sessionId'); } else { AppLogger.d('SyncService', '单个聊天会话 ${session.id} 没有需要同步的元数据更新'); } // ======== 移除: 不再通过 getMessagesForSession 循环发送消息 ======== // 清除此会话的元数据同步标记 await localStorageService.clearSyncFlagByType('chat_session', sessionId); AppLogger.i('SyncService', '单个聊天会话元数据同步处理完成: $sessionId'); return true; } catch (e, stackTrace) { // 捕获所有可能的错误 AppLogger.e('SyncService', '同步单个聊天会话元数据失败 ($sessionId)', e, stackTrace); _updateSyncState(error: '同步单个聊天会话元数据失败: $e'); // 同步失败,暂时不清除标记,留待下次重试 return false; } } /// 解决冲突 (需要根据聊天数据的具体冲突场景来完善) /// 关闭服务,释放资源 void dispose() { // 设置已关闭标志 _isDisposed = true; // 取消网络监听和定时器 _connectivitySubscription?.cancel(); _autoSyncTimer?.cancel(); // 关闭状态流 if (!_syncStateController.isClosed) { _syncStateController.close(); } // 清除当前小说ID,避免后续同步错误 localStorageService.setCurrentNovelId('').then((_) { AppLogger.i('SyncService', '同步服务已关闭,清除当前小说ID'); }); AppLogger.i('SyncService', '同步服务已关闭'); } /// 获取当前用户ID (使用 AppConfig 实现) Future _getCurrentUserId() async { final userId = AppConfig.userId; // 从 AppConfig 获取用户ID if (userId == null || userId.isEmpty) { AppLogger.e('SyncService', '无法获取当前用户ID,同步操作可能失败或无法执行。'); // 根据需求,可以抛出异常或返回占位符/空字符串 // 如果 userId 是必需的,抛出异常更安全 throw SyncException('无法获取当前用户ID,无法执行需要用户ID的同步操作。'); } return userId; } /// 直接设置当前小说ID Future setCurrentNovelId(String novelId) async { // 即使服务已关闭也允许设置,但记录警告 if (_isDisposed) { AppLogger.w('SyncService', '尝试在服务已关闭状态下设置当前小说ID: $novelId'); // 考虑到可能在关闭过程中调用此方法,仍然允许操作继续 } await localStorageService.setCurrentNovelId(novelId); AppLogger.i('SyncService', '同步服务已设置当前小说ID: $novelId'); } } /// 同步状态类 class SyncState { SyncState({ required this.isOnline, // 网络是否连接 required this.isSyncing, // 是否正在同步中 this.error, // 同步错误信息,null表示无错误 this.progress = 0.0, // 同步进度 (0.0 到 1.0) }); /// 空闲状态 (默认在线) factory SyncState.idle({bool online = true}) { return SyncState( isOnline: online, isSyncing: false, ); } /// 同步中状态 factory SyncState.syncing({double progress = 0.0}) { return SyncState( isOnline: true, // 同步时必须在线 isSyncing: true, progress: progress, ); } /// 离线状态 factory SyncState.offline() { return SyncState( isOnline: false, isSyncing: false, // 离线时不能同步 ); } /// 错误状态 (允许指定当时的网络状态) factory SyncState.error(String errorMessage, {bool online = true}) { return SyncState( isOnline: online, // 错误可能在线或离线时发生 isSyncing: false, // 出错时停止同步 error: errorMessage, ); } final bool isOnline; final bool isSyncing; final String? error; final double progress; @override String toString() { // 提供更清晰的状态描述 return 'SyncState(在线: $isOnline, 同步中: $isSyncing, 进度: ${progress.toStringAsFixed(2)}, 错误: ${error ?? "无"})'; } } /// 同步异常类 class SyncException implements Exception { SyncException(this.message); final String message; // 异常信息 @override String toString() => 'SyncException: $message'; }