import 'dart:convert'; import 'dart:isolate'; import 'package:sqlite3/sqlite3.dart'; import 'package:venera/foundation/app.dart'; import 'package:venera/foundation/appdata.dart'; import 'package:venera/foundation/comic_source/comic_source.dart'; import 'package:venera/foundation/comic_type.dart'; import 'package:venera/foundation/favorites.dart'; import 'package:venera/foundation/history.dart'; import 'package:venera/foundation/log.dart'; import 'package:venera/network/cookie_jar.dart'; import 'package:venera/utils/ext.dart'; import 'package:zip_flutter/zip_flutter.dart'; import 'io.dart'; Future exportAppData() async { var time = DateTime.now().millisecondsSinceEpoch ~/ 1000; var cacheFilePath = FilePath.join(App.cachePath, '$time.venera'); var cacheFile = File(cacheFilePath); var dataPath = App.dataPath; if (await cacheFile.exists()) { await cacheFile.delete(); } await Isolate.run(() { var zipFile = ZipFile.open(cacheFilePath); var historyFile = FilePath.join(dataPath, "history.db"); var localFavoriteFile = FilePath.join(dataPath, "local_favorite.db"); var appdata = FilePath.join(dataPath, "appdata.json"); var cookies = FilePath.join(dataPath, "cookie.db"); zipFile.addFile("history.db", historyFile); zipFile.addFile("local_favorite.db", localFavoriteFile); zipFile.addFile("appdata.json", appdata); zipFile.addFile("cookie.db", cookies); for (var file in Directory(FilePath.join(dataPath, "comic_source")).listSync()) { if (file is File) { zipFile.addFile("comic_source/${file.name}", file.path); } } zipFile.close(); }); return cacheFile; } Future importAppData(File file, [bool checkVersion = false]) async { var cacheDirPath = FilePath.join(App.cachePath, 'temp_data'); var cacheDir = Directory(cacheDirPath); if (cacheDir.existsSync()) { cacheDir.deleteSync(recursive: true); } cacheDir.createSync(); try { await Isolate.run(() { ZipFile.openAndExtract(file.path, cacheDirPath); }); var historyFile = cacheDir.joinFile("history.db"); var localFavoriteFile = cacheDir.joinFile("local_favorite.db"); var appdataFile = cacheDir.joinFile("appdata.json"); var cookieFile = cacheDir.joinFile("cookie.db"); if (checkVersion && appdataFile.existsSync()) { var data = jsonDecode(await appdataFile.readAsString()); var version = data["settings"]["dataVersion"]; if (version is int && version <= appdata.settings["dataVersion"]) { return; } } if (await historyFile.exists()) { HistoryManager().close(); File(FilePath.join(App.dataPath, "history.db")).deleteIfExistsSync(); historyFile.renameSync(FilePath.join(App.dataPath, "history.db")); HistoryManager().init(); } if (await localFavoriteFile.exists()) { LocalFavoritesManager().close(); File(FilePath.join(App.dataPath, "local_favorite.db")) .deleteIfExistsSync(); localFavoriteFile .renameSync(FilePath.join(App.dataPath, "local_favorite.db")); LocalFavoritesManager().init(); } if (await appdataFile.exists()) { var content = await appdataFile.readAsString(); var data = jsonDecode(content); appdata.syncData(data); } if (await cookieFile.exists()) { SingleInstanceCookieJar.instance?.dispose(); File(FilePath.join(App.dataPath, "cookie.db")).deleteIfExistsSync(); cookieFile.renameSync(FilePath.join(App.dataPath, "cookie.db")); SingleInstanceCookieJar.instance = SingleInstanceCookieJar(FilePath.join(App.dataPath, "cookie.db")) ..init(); } var comicSourceDir = FilePath.join(cacheDirPath, "comic_source"); if (Directory(comicSourceDir).existsSync()) { for (var file in Directory(comicSourceDir).listSync()) { if (file is File) { var targetFile = FilePath.join(App.dataPath, "comic_source", file.name); File(targetFile).deleteIfExistsSync(); await file.copy(targetFile); } } await ComicSource.reload(); } } finally { cacheDir.deleteIgnoreError(recursive: true); } } Future importPicaData(File file) async { var cacheDirPath = FilePath.join(App.cachePath, 'temp_data'); var cacheDir = Directory(cacheDirPath); if (cacheDir.existsSync()) { cacheDir.deleteSync(recursive: true); } cacheDir.createSync(); try { await Isolate.run(() { ZipFile.openAndExtract(file.path, cacheDirPath); }); var localFavoriteFile = cacheDir.joinFile("local_favorite.db"); if (localFavoriteFile.existsSync()) { var db = sqlite3.open(localFavoriteFile.path); try { var folderNames = db .select("SELECT name FROM sqlite_master WHERE type='table';") .map((e) => e["name"] as String) .toList(); folderNames .removeWhere((e) => e == "folder_order" || e == "folder_sync"); for (var folderSyncValue in db.select("SELECT * FROM folder_sync;")) { var folderName = folderSyncValue["folder_name"]; String sourceKey = folderSyncValue["key"]; sourceKey = sourceKey.toLowerCase() == "htmanga" ? "wnacg" : sourceKey; // 有值就跳过 if (LocalFavoritesManager().findLinked(folderName).$1 != null) { continue; } try { LocalFavoritesManager().linkFolderToNetwork(folderName, sourceKey, jsonDecode(folderSyncValue["sync_data"])["folderId"]); } catch (e, stack) { Log.error(e.toString(), stack); } } for (var folderName in folderNames) { if (!LocalFavoritesManager().existsFolder(folderName)) { LocalFavoritesManager().createFolder(folderName); } for (var comic in db.select("SELECT * FROM \"$folderName\";")) { LocalFavoritesManager().addComic( folderName, FavoriteItem( id: comic['target'], name: comic['name'], coverPath: comic['cover_path'], author: comic['author'], type: ComicType(switch (comic['type']) { 0 => 'picacg'.hashCode, 1 => 'ehentai'.hashCode, 2 => 'jm'.hashCode, 3 => 'hitomi'.hashCode, 4 => 'wnacg'.hashCode, 6 => 'nhentai'.hashCode, _ => comic['type'] }), tags: comic['tags'].split(','), ), ); } } } catch (e) { Log.error("Import Data", "Failed to import local favorite: $e"); } finally { db.dispose(); } } var historyFile = cacheDir.joinFile("history.db"); if (historyFile.existsSync()) { var db = sqlite3.open(historyFile.path); try { for (var comic in db.select("SELECT * FROM history;")) { HistoryManager().addHistory( History.fromMap({ "type": switch (comic['type']) { 0 => 'picacg'.hashCode, 1 => 'ehentai'.hashCode, 2 => 'jm'.hashCode, 3 => 'hitomi'.hashCode, 4 => 'wnacg'.hashCode, 5 => 'nhentai'.hashCode, _ => comic['type'] }, "id": comic['target'], "max_page": comic["max_page"], "ep": comic["ep"], "page": comic["page"], "time": comic["time"], "title": comic["title"], "subtitle": comic["subtitle"], "cover": comic["cover"], "readEpisode": [comic["ep"]], }), ); } List imageFavoritesComicList = ImageFavoriteManager().comics; for (var comic in db.select("SELECT * FROM image_favorites;")) { String sourceKey = comic["id"].split("-")[0]; // 换名字了, 绅士漫画 if (sourceKey.toLowerCase() == "htmanga") { sourceKey = "wnacg"; } if (ComicSource.find(sourceKey) == null) { continue; } String id = comic["id"].split("-")[1]; int page = comic["page"]; // 章节和page是从1开始的, pica 可能有从 0 开始的, 得转一下 int ep = comic["ep"] == 0 ? 1 : comic["ep"]; String title = comic["title"]; String epName = ""; ImageFavoritesComic? tempComic = imageFavoritesComicList .firstWhereOrNull((e) => e.id == id && e.sourceKey == sourceKey); ImageFavorite curImageFavorite = ImageFavorite(page, "", null, "", id, ep, sourceKey, epName); if (tempComic == null) { tempComic = ImageFavoritesComic(id, [], title, sourceKey, [], [], DateTime.now(), "", {}, "", 1); tempComic.imageFavoritesEp = [ ImageFavoritesEp("", ep, [curImageFavorite], epName, 1) ]; imageFavoritesComicList.add(tempComic); } else { ImageFavoritesEp? tempEp = tempComic.imageFavoritesEp.firstWhereOrNull((e) => e.ep == ep); if (tempEp == null) { tempComic.imageFavoritesEp .add(ImageFavoritesEp("", ep, [curImageFavorite], epName, 1)); } else { // 如果已经有这个page了, 就不添加了 if (tempEp.imageFavorites .firstWhereOrNull((e) => e.page == page) == null) { tempEp.imageFavorites.add(curImageFavorite); } } } } for (var temp in imageFavoritesComicList) { ImageFavoriteManager().addOrUpdateOrDelete( temp, temp == imageFavoritesComicList.last, ); } } catch (e, stack) { Log.error("Import Data", "Failed to import history: $e", stack); } finally { db.dispose(); } } } finally { cacheDir.deleteIgnoreError(recursive: true); } }