diff --git a/lib/services/sync/auto_backup_service.dart b/lib/services/sync/auto_backup_service.dart index e3395f2..02d18f0 100644 --- a/lib/services/sync/auto_backup_service.dart +++ b/lib/services/sync/auto_backup_service.dart @@ -79,19 +79,20 @@ class AutoBackupService { // 导出数据 final result = await BackupService.instance.exportDataForAutoBackup(); - - if (!result.success) { + + if (!result.success || result.zipPath == null) { debugPrint('AutoBackup: 导出失败 - ${result.errorMessage}'); return; } - + // 生成备份文件名 final fileName = 'auto_backup_${_formatDateTime(DateTime.now())}.zip'; final backupFile = File(path.join(backupDir.path, fileName)); - - // 写入备份文件 - await backupFile.writeAsBytes(result.zipBytes!); - + + // 移动临时 zip 到备份目录 + final tempFile = File(result.zipPath!); + await tempFile.rename(backupFile.path); + debugPrint('AutoBackup: 备份成功 - ${backupFile.path}'); // 清理旧备份,只保留最新的5个 diff --git a/lib/services/sync/backup_service.dart b/lib/services/sync/backup_service.dart index c109db3..3c64d58 100644 --- a/lib/services/sync/backup_service.dart +++ b/lib/services/sync/backup_service.dart @@ -158,13 +158,9 @@ class BackupService { encoder.close(); - // 读取最终 zip 文件 - final zipFile = File(tempZipPath); - final zipBytes = await zipFile.readAsBytes(); - await zipFile.delete(); - + // 返回 zip 文件路径,不再读入内存 return _ExportData( - zipBytes: Uint8List.fromList(zipBytes), + zipPath: tempZipPath, movieCount: movies.length, bookCount: books.length, noteCount: notes.length, @@ -184,31 +180,36 @@ class BackupService { Future exportDataWithImages() async { try { final data = await _buildExportData(); + final zipFile = File(data.zipPath!); final tempDir = await getTemporaryDirectory(); final fileName = 'mooknote_backup_${_formatDateTime(DateTime.now())}.zip'; final tempFilePath = path.join(tempDir.path, fileName); - await File(tempFilePath).writeAsBytes(data.zipBytes); String? finalPath; try { + // FilePicker.saveFile 需要 bytes,这里必须读入内存 + final zipBytes = await zipFile.readAsBytes(); final outputPath = await FilePicker.platform.saveFile( dialogTitle: '保存备份文件', fileName: fileName, type: FileType.custom, allowedExtensions: ['zip'], - bytes: data.zipBytes, + bytes: zipBytes, ); if (outputPath == null) { + await zipFile.delete(); return ExportResult.cancelled(); } finalPath = outputPath; - if (finalPath != tempFilePath) { - await File(finalPath).writeAsBytes(data.zipBytes); - } } catch (e) { + // FilePicker 不可用时,复制到临时目录 + await zipFile.copy(tempFilePath); finalPath = tempFilePath; } + // 清理原始临时 zip + try { await zipFile.delete(); } catch (_) {} + return ExportResult.success( filePath: finalPath, movieCount: data.movieCount, @@ -229,12 +230,12 @@ class BackupService { // ─── 自动备份导出 ───────────────────────────────────── - /// 导出数据用于自动备份(返回字节数据) + /// 导出数据用于自动备份(返回临时 zip 文件路径,调用方负责删除) Future exportDataForAutoBackup() async { try { final data = await _buildExportData(); return AutoBackupExportResult.success( - zipBytes: data.zipBytes, + zipPath: data.zipPath!, movieCount: data.movieCount, bookCount: data.bookCount, noteCount: data.noteCount, @@ -776,7 +777,7 @@ class BackupService { /// 导出中间数据 class _ExportData { - final Uint8List zipBytes; + final String? zipPath; // 临时 zip 文件路径,调用方负责删除 final int movieCount; final int bookCount; final int noteCount; @@ -784,7 +785,7 @@ class _ExportData { final int epubCount; _ExportData({ - required this.zipBytes, + this.zipPath, required this.movieCount, required this.bookCount, required this.noteCount, @@ -798,7 +799,7 @@ class _ExportData { class AutoBackupExportResult { final bool success; final String? errorMessage; - final Uint8List? zipBytes; + final String? zipPath; // 临时 zip 文件路径,调用方负责删除 final int movieCount; final int bookCount; final int noteCount; @@ -808,7 +809,7 @@ class AutoBackupExportResult { AutoBackupExportResult._({ required this.success, this.errorMessage, - this.zipBytes, + this.zipPath, this.movieCount = 0, this.bookCount = 0, this.noteCount = 0, @@ -817,7 +818,7 @@ class AutoBackupExportResult { }); factory AutoBackupExportResult.success({ - required Uint8List zipBytes, + required String zipPath, required int movieCount, required int bookCount, required int noteCount, @@ -825,7 +826,7 @@ class AutoBackupExportResult { int epubCount = 0, }) { return AutoBackupExportResult._( - success: true, zipBytes: zipBytes, + success: true, zipPath: zipPath, movieCount: movieCount, bookCount: bookCount, noteCount: noteCount, imageCount: imageCount, epubCount: epubCount, diff --git a/lib/services/sync/webdav_service.dart b/lib/services/sync/webdav_service.dart index 70e540d..b358e12 100644 --- a/lib/services/sync/webdav_service.dart +++ b/lib/services/sync/webdav_service.dart @@ -222,13 +222,15 @@ class WebDAVService { try { if (direction == SyncDirection.upload) { final exportResult = await BackupService.instance.exportDataForAutoBackup(); - if (!exportResult.success || exportResult.zipBytes == null) { + if (!exportResult.success || exportResult.zipPath == null) { return SyncResult(success: false, message: exportResult.errorMessage ?? '创建备份失败'); } final fileName = _generateBackupFileName(); final zipUrl = '$dirUrl/$fileName'; - final success = await _uploadBytes(client, zipUrl, username, password, exportResult.zipBytes!); + final success = await _uploadFile(client, zipUrl, username, password, exportResult.zipPath!); + // 清理临时 zip 文件 + try { await File(exportResult.zipPath!).delete(); } catch (_) {} if (success) { uploadedFiles = 1; uploadedImages = exportResult.imageCount; @@ -314,9 +316,11 @@ class WebDAVService { // 上传本地备份(无论是否下载,确保远程有最新数据) final exportResult = await BackupService.instance.exportDataForAutoBackup(); - if (exportResult.success && exportResult.zipBytes != null) { + if (exportResult.success && exportResult.zipPath != null) { final fileName = _generateBackupFileName(); - final uploadSuccess = await _uploadBytes(client, '$dirUrl/$fileName', username, password, exportResult.zipBytes!); + final uploadSuccess = await _uploadFile(client, '$dirUrl/$fileName', username, password, exportResult.zipPath!); + // 清理临时 zip 文件 + try { await File(exportResult.zipPath!).delete(); } catch (_) {} if (uploadSuccess) { uploadedFiles = 1; uploadedImages = exportResult.imageCount; @@ -361,15 +365,22 @@ class WebDAVService { return prefs.getString(_lastSyncKey); } - /// 上传字节数据到 WebDAV - Future _uploadBytes( + /// 上传文件到 WebDAV(从文件路径读取,避免备份服务端重复占用内存) + Future _uploadFile( http.Client client, String url, String username, String password, - Uint8List bytes, + String filePath, ) async { try { + final file = File(filePath); + if (!await file.exists()) { + debugPrint('[WebDAV] _uploadFile: 文件不存在 $filePath'); + return false; + } + final bytes = await file.readAsBytes(); + var request = http.Request('PUT', Uri.parse(url)); request.headers['Authorization'] = _basicAuth(username, password); request.headers['Content-Type'] = 'application/zip'; @@ -394,7 +405,7 @@ class WebDAVService { debugPrint('[WebDAV] PUT $url -> ${response.statusCode}'); return response.statusCode == 200 || response.statusCode == 201 || response.statusCode == 204; } catch (e) { - debugPrint('[WebDAV] _uploadBytes error: $e'); + debugPrint('[WebDAV] _uploadFile error: $e'); return false; } }