Files
org.eu.mumulhl.ciyue/lib/services/backup.dart
T
2026-01-24 12:21:03 +08:00

199 lines
6.1 KiB
Dart

import "dart:convert";
import "dart:io";
import "package:ciyue/core/app_globals.dart";
import "package:ciyue/core/app_router.dart";
import "package:ciyue/database/app/daos.dart";
import "package:ciyue/models/backup/backup.dart";
import "package:ciyue/services/platform.dart";
import "package:ciyue/repositories/settings.dart";
import "package:ciyue/viewModels/wordbook.dart";
import "package:file_selector/file_selector.dart";
import "package:path/path.dart";
import "package:provider/provider.dart";
abstract class BackupFileHandler {
bool get isAndroid;
Future<void> writeAutoExportAndroid(
String directory, String fileName, String content);
Future<void> writeAutoExportDesktop(String path, String content);
Future<void> writeManualExportAndroid(String content);
Future<void> writeManualExportDesktop(String content);
Future<String?> readImportFile();
}
class DefaultBackupFileHandler implements BackupFileHandler {
@override
bool get isAndroid => Platform.isAndroid;
@override
Future<void> writeAutoExportAndroid(
String directory, String fileName, String content) async {
final path = join(directory, fileName);
await PlatformMethod.writeFile({
"path": path,
"directory": directory,
"filename": fileName,
"content": content,
});
}
@override
Future<void> writeAutoExportDesktop(String path, String content) async {
final file = File(join(path));
await file.writeAsString(content);
}
@override
Future<void> writeManualExportAndroid(String content) async {
await PlatformMethod.createFile(content);
}
@override
Future<void> writeManualExportDesktop(String content) async {
final result = await getSaveLocation(suggestedName: "ciyue.json");
if (result != null) {
final file = File(result.path);
await file.writeAsString(content);
}
}
@override
Future<String?> readImportFile() async {
const XTypeGroup typeGroup = XTypeGroup(
label: "json",
extensions: <String>["json"],
);
final xFile = await openFile(acceptedTypeGroups: [typeGroup]);
if (xFile == null) {
return null;
}
return await xFile.readAsString();
}
}
class BackupService {
final WordbookDao wordbookDao;
final WordbookTagsDao wordbookTagsDao;
final HistoryDao historyDao;
final WritingCheckHistoryDao writingCheckHistoryDao;
final TranslateHistoryDao translateHistoryDao;
final BackupFileHandler fileHandler;
final WordbookModel? _wordbookModel;
BackupService({
required this.wordbookDao,
required this.wordbookTagsDao,
required this.historyDao,
required this.writingCheckHistoryDao,
required this.translateHistoryDao,
required this.fileHandler,
WordbookModel? wordbookModel,
}) : _wordbookModel = wordbookModel;
Future<void> export({
required bool autoExport,
required String exportFileName,
String? exportDirectory,
String? exportPath,
}) async {
final words = await wordbookDao.getAllWords(),
tags = await wordbookTagsDao.getAllTags(),
history = await historyDao.getAllHistory(),
writingCheckHistory = await writingCheckHistoryDao.getAllHistory(),
translateHistory = await translateHistoryDao.getAllHistory();
if (words.isNotEmpty ||
history.isNotEmpty ||
writingCheckHistory.isNotEmpty ||
translateHistory.isNotEmpty) {
final backupData = BackupData(
version: Backup.version,
wordbookWords: words,
wordbookTags: tags,
history: history.map((e) => e.word).toList(),
writingCheckHistory: writingCheckHistory,
translateHistory: translateHistory,
);
final jsonContent = backupData.toJson();
if (autoExport) {
if (fileHandler.isAndroid) {
final fileName = setExtension(exportFileName, ".json");
if (exportDirectory != null) {
await fileHandler.writeAutoExportAndroid(
exportDirectory, fileName, jsonContent);
}
} else {
if (exportPath != null) {
await fileHandler.writeAutoExportDesktop(exportPath, jsonContent);
}
}
return;
}
if (fileHandler.isAndroid) {
await fileHandler.writeManualExportAndroid(jsonContent);
} else {
await fileHandler.writeManualExportDesktop(jsonContent);
}
}
}
Future<void> import() async {
final contentString = await fileHandler.readImportFile();
if (contentString == null) {
return;
}
final Map<String, dynamic> content = jsonDecode(contentString);
final backupData = BackupData.fromJson(content);
final wordbookModel = _wordbookModel ??
Provider.of<WordbookModel>(navigatorKey.currentContext!, listen: false);
await wordbookModel.addAllWords(backupData.wordbookWords);
await wordbookTagsDao.addAllTags(backupData.wordbookTags);
// Import history: add from oldest to newest to preserve order (since addHistory adds to top)
for (final word in backupData.history.reversed) {
await historyDao.addHistory(word);
}
await writingCheckHistoryDao.addAllHistory(backupData.writingCheckHistory);
await translateHistoryDao.addAllHistory(backupData.translateHistory);
}
}
class Backup {
static const version = 1;
static Future<void> export(bool autoExport) async {
final service = BackupService(
wordbookDao: wordbookDao,
wordbookTagsDao: wordbookTagsDao,
historyDao: historyDao,
writingCheckHistoryDao: writingCheckHistoryDao,
translateHistoryDao: translateHistoryDao,
fileHandler: DefaultBackupFileHandler(),
);
await service.export(
autoExport: autoExport,
exportFileName: settings.exportFileName,
exportDirectory: settings.exportDirectory,
exportPath: settings.exportPath,
);
}
static Future<void> import() async {
final service = BackupService(
wordbookDao: wordbookDao,
wordbookTagsDao: wordbookTagsDao,
historyDao: historyDao,
writingCheckHistoryDao: writingCheckHistoryDao,
translateHistoryDao: translateHistoryDao,
fileHandler: DefaultBackupFileHandler(),
);
await service.import();
}
}