From 204c4c8573eb278d778dc12c355cc199fd982e2d Mon Sep 17 00:00:00 2001 From: troyeguo <13820674+troyeguo@users.noreply.github.com> Date: Tue, 19 May 2026 17:20:17 +0800 Subject: [PATCH] feat(backup): enhance backup functionality to include dict, background, and snapshot directories refactor(common): optimize cover processing in upgradeStorage function refactor(configUtil): expand config list to include dict and background configurations refactor(coverUtil): update cover processing to handle async operations refactor(restore): improve restore functionality to support additional asset types --- src/utils/file/backup.ts | 35 ++++++++++++++++++++++++++--------- src/utils/file/common.ts | 16 ++++++++-------- src/utils/file/configUtil.ts | 9 ++++++++- src/utils/file/coverUtil.ts | 14 ++++++++++---- src/utils/file/restore.ts | 29 +++++------------------------ 5 files changed, 57 insertions(+), 46 deletions(-) diff --git a/src/utils/file/backup.ts b/src/utils/file/backup.ts index f3dd5591..27e97b14 100644 --- a/src/utils/file/backup.ts +++ b/src/utils/file/backup.ts @@ -2,13 +2,17 @@ import BookUtil from "./bookUtil"; import { isElectron } from "react-device-detect"; import { checkMissingBook, getStorageLocation } from "../common"; import CoverUtil from "./coverUtil"; -import { CommonTool } from "../../assets/lib/kookit-extra-browser.min"; +import { + CommonTool, + ConfigService, +} from "../../assets/lib/kookit-extra-browser.min"; import { getCloudConfig } from "./common"; import DatabaseService from "../storage/databaseService"; import { saveAs } from "file-saver"; import JSZip from "jszip"; import ConfigUtil from "./configUtil"; import SyncService from "../storage/syncService"; +import BackgroundUtil from "./backgroundUtil"; import toast from "react-hot-toast"; import i18n from "../../i18n"; @@ -219,8 +223,8 @@ export const backupFromPath = async ( } }; - // Add book and cover directories - for (const dir of ["book", "cover"]) { + // Add book, cover, dict, background, snapshot directories + for (const dir of ["book", "cover", "dict", "background", "snapshot"]) { const sourceDir = path.join(dataPath, dir); if (fs.existsSync(sourceDir)) { addDirectoryToZip(zip, sourceDir, dir); @@ -297,6 +301,7 @@ export const backupFromStorage = async () => { let sync = JSON.stringify(await ConfigUtil.dumpConfig("sync")); await zipCover(zip); await zipBook(zip); + await zipBackground(zip); let result = await zipConfig( zip, books, @@ -381,17 +386,29 @@ export const zipCover = async (zip: any) => { } else { for (let i = 0; i < books.length; i++) { let cover = await CoverUtil.getCover(books[i]); - if (cover.startsWith("blob")) { - let response = await fetch(cover); - let blob = await response.blob(); - cover = await CoverUtil.blobToBase64(blob); - } - const result = CoverUtil.convertCoverBase64(cover); + const result = await CoverUtil.convertCoverBase64(cover); coverZip.file(`${books[i].key}.${result.extension}`, result.arrayBuffer); } } }; +export const zipBackground = async (zip: any) => { + const backgroundIds = ConfigService.getAllListConfig("backgroundList") || []; + const bgZip = zip.folder("background"); + for (const id of backgroundIds) { + const meta = BackgroundUtil.getImageMeta(id); + if (!meta) continue; + try { + const dataUrl = await BackgroundUtil.loadImage(id, meta.extension); + if (!dataUrl) continue; + const { arrayBuffer, extension } = BackgroundUtil.convertDataUrl(dataUrl); + bgZip.file(`${id}.${extension}`, arrayBuffer); + } catch (error) { + console.error(`Failed to backup background ${id}:`, error); + } + } +}; + export const zipConfig = ( zip: any, bookBuffer: ArrayBuffer, diff --git a/src/utils/file/common.ts b/src/utils/file/common.ts index 7f64e97b..a6ba20bd 100644 --- a/src/utils/file/common.ts +++ b/src/utils/file/common.ts @@ -132,21 +132,21 @@ export const upgradeStorage = async ( fs.mkdirSync(path.join(dataPath, "cover"), { recursive: true }); let books: Book[] | null = await localforage.getItem("books"); if (books && books.length > 0) { - books.forEach((item) => { - let cover = item.cover; + for (let i = 0; i < books.length; i++) { + let cover = books[i].cover; if (cover) { - let result = CoverUtil.convertCoverBase64(cover); + let result = await CoverUtil.convertCoverBase64(cover); fs.writeFileSync( - path.join(dataPath, "cover", `${item.key}.${result.extension}`), + path.join(dataPath, "cover", `${books[i].key}.${result.extension}`), Buffer.from(result.arrayBuffer) ); - item.cover = ""; + books[i].cover = ""; } //fix sqlite3 text issue - if (typeof item.author !== "string") { - item.author = ""; + if (typeof books[i].author !== "string") { + books[i].author = ""; } - }); + } await DatabaseService.saveAllRecords(books, "books"); } diff --git a/src/utils/file/configUtil.ts b/src/utils/file/configUtil.ts index 912dd403..6ba08368 100644 --- a/src/utils/file/configUtil.ts +++ b/src/utils/file/configUtil.ts @@ -470,7 +470,14 @@ class ConfigUtil { config = ConfigService.getAllSyncRecord(); } else { let configList = CommonTool.configList; - configList = [...configList, "readerConfig"]; + configList = [ + ...configList, + "dictList", + "backgroundList", + "readerConfig", + "customBackgrounds", + "customDicts", + ]; for (let i = 0; i < configList.length; i++) { let item = configList[i]; if (ConfigService.getItem(item)) { diff --git a/src/utils/file/coverUtil.ts b/src/utils/file/coverUtil.ts index 027a1270..b8461d6d 100644 --- a/src/utils/file/coverUtil.ts +++ b/src/utils/file/coverUtil.ts @@ -206,7 +206,7 @@ class CoverUtil { if (!fs.existsSync(directoryPath)) { fs.mkdirSync(directoryPath, { recursive: true }); } - const result = this.convertCoverBase64(book.cover); + const result = await this.convertCoverBase64(book.cover); fs.writeFileSync( path.join(directoryPath, `${book.key}.${result.extension}`), Buffer.from(result.arrayBuffer) @@ -217,7 +217,7 @@ class CoverUtil { book.cover = ""; } else { if (ConfigService.getReaderConfig("isUseLocal") === "yes") { - let result = this.convertCoverBase64(coverBase64); + let result = await this.convertCoverBase64(coverBase64); await LocalFileManager.saveFile( `${book.key}.${result.extension}`, result.arrayBuffer, @@ -230,7 +230,12 @@ class CoverUtil { // book.cover = ""; } } - static convertCoverBase64(base64: string) { + static async convertCoverBase64(base64: string) { + if (base64.startsWith("blob") || base64.startsWith("http")) { + let response = await fetch(base64); + let blob = await response.blob(); + base64 = await CoverUtil.blobToBase64(blob); + } let extension = this.base64ToFileType(base64); const base64Data = base64.replace(/^data:.*;base64,/, ""); @@ -377,7 +382,8 @@ class CoverUtil { await syncUtil.uploadFile(cover, "cover", coverBuffer); } else { if (book && book.cover) { - let result = this.convertCoverBase64(book.cover); + let base64 = book.cover; + let result = await this.convertCoverBase64(base64); let coverBlob = new Blob([result.arrayBuffer], { type: `image/${result.extension}`, }); diff --git a/src/utils/file/restore.ts b/src/utils/file/restore.ts index e926cf7c..547b9955 100644 --- a/src/utils/file/restore.ts +++ b/src/utils/file/restore.ts @@ -189,14 +189,6 @@ export const restoreFromfilePath = async (filePath: string) => { toast.loading(i18n.t("Restoring...") + ` (${percent}%)`, { id: "backup" }); }; - const streamToBuffer = (stream: any): Promise => - new Promise((res, rej) => { - const chunks: any[] = []; - stream.on("data", (chunk: any) => chunks.push(chunk)); - stream.on("end", () => res(Buffer.concat(chunks))); - stream.on("error", rej); - }); - const streamToFile = (stream: any, dest: string): Promise => new Promise((res, rej) => { const dir = path.dirname(dest); @@ -250,7 +242,11 @@ export const restoreFromfilePath = async (filePath: string) => { const assetFiles = Object.keys(zip.files).filter( (name) => !zip.files[name].dir && - (name.startsWith("book/") || name.startsWith("cover/")) + (name.startsWith("book/") || + name.startsWith("cover/") || + name.startsWith("dict/") || + name.startsWith("background/") || + name.startsWith("snapshot/")) ); await Promise.all( assetFiles.map(async (fileName) => { @@ -287,20 +283,6 @@ export const restoreFromOldBackup = async (zipEntries: any) => { return false; } }; -export const restoreFromNewBackup = async (zipEntries: any) => { - let result = await unzipConfig(zipEntries); - if (result) { - let res1 = await unzipBook(zipEntries); - let res2 = await unzipCover(zipEntries); - if (res1 || res2) { - return true; - } else { - return false; - } - } else { - return false; - } -}; export const unzipConfig = async (zipEntries: any) => { const fs = window.require("fs"); const path = window.require("path"); @@ -416,7 +398,6 @@ export const unzipCover = async (zipEntries: any) => { } return flag; }; - export const unzipOldConfig = (zipEntries: any) => { return new Promise((resolve) => { zipEntries.forEach(function (zipEntry) {