mirror of
https://github.com/koodo-reader/koodo-reader.git
synced 2026-06-16 20:01:17 -04:00
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
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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}`,
|
||||
});
|
||||
|
||||
@@ -189,14 +189,6 @@ export const restoreFromfilePath = async (filePath: string) => {
|
||||
toast.loading(i18n.t("Restoring...") + ` (${percent}%)`, { id: "backup" });
|
||||
};
|
||||
|
||||
const streamToBuffer = (stream: any): Promise<Buffer> =>
|
||||
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<void> =>
|
||||
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<boolean>((resolve) => {
|
||||
zipEntries.forEach(function (zipEntry) {
|
||||
|
||||
Reference in New Issue
Block a user