Files
backup-manager/preload.js

347 lines
11 KiB
JavaScript
Raw Normal View History

2026-02-01 15:12:59 +08:00
const { contextBridge, ipcRenderer } = require("electron");
const path = require("path");
const fs = require("fs");
// ============ 数据库管理纯JSON实现无需sql.js============
class SimpleDB {
constructor(dataPath) {
this.dataPath = dataPath;
this.currentDbName = null;
this.currentData = null;
}
getDbPath(name) {
return path.join(this.dataPath, `${name}.json`);
}
getStoragePath(name) {
return path.join(this.dataPath, name);
}
listDatabases() {
if (!fs.existsSync(this.dataPath)) return [];
const files = fs.readdirSync(this.dataPath);
const databases = [];
for (const file of files) {
if (file.endsWith(".json")) {
const name = file.replace(".json", "");
try {
const data = JSON.parse(fs.readFileSync(this.getDbPath(name), "utf8"));
const totalSize = data.backups.reduce((sum, b) => sum + (b.file_size || 0), 0);
databases.push({ name, count: data.backups.length, totalSize });
} catch {
databases.push({ name, count: 0, totalSize: 0 });
}
}
}
return databases;
}
createDatabase(name) {
const dbPath = this.getDbPath(name);
const storagePath = this.getStoragePath(name);
if (fs.existsSync(dbPath)) {
throw new Error(`数据库 "${name}" 已存在`);
}
fs.mkdirSync(storagePath, { recursive: true });
fs.writeFileSync(dbPath, JSON.stringify({ backups: [] }, null, 2), "utf8");
return true;
}
deleteDatabase(name) {
const dbPath = this.getDbPath(name);
const storagePath = this.getStoragePath(name);
if (this.currentDbName === name) {
this.currentDbName = null;
this.currentData = null;
}
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
if (fs.existsSync(storagePath)) {
fs.rmSync(storagePath, { recursive: true, force: true });
}
return true;
}
renameDatabase(oldName, newName) {
const oldDbPath = this.getDbPath(oldName);
const newDbPath = this.getDbPath(newName);
const oldStoragePath = this.getStoragePath(oldName);
const newStoragePath = this.getStoragePath(newName);
if (fs.existsSync(newDbPath)) {
throw new Error(`数据库 "${newName}" 已存在`);
}
// 读取并更新数据
const data = JSON.parse(fs.readFileSync(oldDbPath, "utf8"));
data.backups = data.backups.map(b => ({
...b,
filename: b.filename.replace(oldName, newName)
}));
// 保存新数据库
fs.writeFileSync(newDbPath, JSON.stringify(data, null, 2), "utf8");
fs.unlinkSync(oldDbPath);
// 重命名存储目录和文件
if (fs.existsSync(oldStoragePath)) {
fs.renameSync(oldStoragePath, newStoragePath);
const files = fs.readdirSync(newStoragePath);
for (const file of files) {
if (file.startsWith(oldName)) {
const newFilename = file.replace(oldName, newName);
fs.renameSync(
path.join(newStoragePath, file),
path.join(newStoragePath, newFilename)
);
}
}
}
if (this.currentDbName === oldName) {
this.currentDbName = newName;
this.currentData = data;
}
return true;
}
openDatabase(name) {
const dbPath = this.getDbPath(name);
if (!fs.existsSync(dbPath)) {
throw new Error(`数据库 "${name}" 不存在`);
}
this.currentDbName = name;
this.currentData = JSON.parse(fs.readFileSync(dbPath, "utf8"));
return true;
}
saveDatabase() {
if (this.currentDbName && this.currentData) {
fs.writeFileSync(
this.getDbPath(this.currentDbName),
JSON.stringify(this.currentData, null, 2),
"utf8"
);
}
}
getBackups() {
if (!this.currentData) return [];
return [...this.currentData.backups].sort((a, b) =>
b.date_modified.localeCompare(a.date_modified)
);
}
findByDateModified(dateModified) {
if (!this.currentData) return null;
return this.currentData.backups.find(b => b.date_modified === dateModified) || null;
}
upsertBackup(info) {
if (!this.currentData) throw new Error("没有打开的数据库");
const idx = this.currentData.backups.findIndex(b => b.date_modified === info.date_modified);
if (idx >= 0) {
this.currentData.backups[idx] = { ...this.currentData.backups[idx], ...info };
this.saveDatabase();
return { action: "updated", id: idx };
} else {
const id = Date.now();
this.currentData.backups.push({ id, ...info });
this.saveDatabase();
return { action: "inserted", id };
}
}
updateTag(id, tag) {
if (!this.currentData) throw new Error("没有打开的数据库");
const backup = this.currentData.backups.find(b => b.id === id);
if (backup) {
backup.tag = tag;
this.saveDatabase();
}
return true;
}
deleteBackup(id) {
if (!this.currentData) throw new Error("没有打开的数据库");
const idx = this.currentData.backups.findIndex(b => b.id === id);
if (idx >= 0) {
const backup = this.currentData.backups[idx];
const filePath = path.join(this.getStoragePath(this.currentDbName), backup.filename);
if (fs.existsSync(filePath)) fs.unlinkSync(filePath);
this.currentData.backups.splice(idx, 1);
this.saveDatabase();
}
return true;
}
getCurrentDbName() { return this.currentDbName; }
getCurrentStoragePath() {
return this.currentDbName ? this.getStoragePath(this.currentDbName) : null;
}
}
// ============ 压缩工具 ============
const archiver = require("archiver");
function compressFolder(folderPath, outputPath) {
return new Promise((resolve, reject) => {
const output = fs.createWriteStream(outputPath);
const archive = archiver("zip", { zlib: { level: 9 } });
output.on("close", () => resolve({ size: archive.pointer(), path: outputPath }));
archive.on("error", reject);
archive.pipe(output);
archive.directory(folderPath, false);
archive.finalize();
});
}
function copyArchive(source, dest) {
return new Promise((resolve, reject) => {
fs.copyFile(source, dest, err => err ? reject(err) : resolve(dest));
});
}
function isSupportedArchive(filePath) {
const ext = path.extname(filePath).toLowerCase();
return [".zip", ".rar", ".7z", ".tar", ".gz"].includes(ext);
}
function getArchiveType(filePath) {
return path.extname(filePath).toLowerCase().replace(".", "");
}
// ============ 文件工具 ============
function getLatestModifiedTime(folderPath) {
let latest = new Date(0);
function scan(dir) {
const items = fs.readdirSync(dir);
for (const item of items) {
const itemPath = path.join(dir, item);
const stats = fs.statSync(itemPath);
if (stats.isDirectory()) {
scan(itemPath);
} else if (stats.mtime > latest) {
latest = stats.mtime;
}
}
}
scan(folderPath);
return latest;
}
function formatDateModified(date) {
const d = new Date(date);
const pad = n => String(n).padStart(2, "0");
return `${d.getFullYear()}${pad(d.getMonth() + 1)}${pad(d.getDate())}${pad(d.getHours())}${pad(d.getMinutes())}`;
}
function formatDateDisplay(str) {
if (!str || str.length !== 12) return str;
return `${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)} ${str.slice(8, 10)}:${str.slice(10, 12)}`;
}
function formatFileSize(bytes) {
if (!bytes) return "0 B";
const units = ["B", "KB", "MB", "GB"];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, i)).toFixed(1) + " " + units[i];
}
function generateBackupFilename(dbName, dateModified, archiveType) {
return `${dbName}-${dateModified}.${archiveType}`;
}
function getBaseName(filename) {
return path.basename(filename, path.extname(filename));
}
function isNameMatching(filename, dbName) {
return getBaseName(filename).toLowerCase().startsWith(dbName.toLowerCase());
}
function ensureDir(dirPath) {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
return dirPath;
}
// ============ 初始化数据库 ============
let db = null;
// ============ 暴露API ============
contextBridge.exposeInMainWorld("electronAPI", {
// 窗口控制
minimize: () => ipcRenderer.invoke("window-minimize"),
maximize: () => ipcRenderer.invoke("window-maximize"),
close: () => ipcRenderer.invoke("window-close"),
// 路径
getDataPath: () => ipcRenderer.invoke("get-data-path"),
selectExportDir: () => ipcRenderer.invoke("select-export-dir"),
showInFolder: (p) => ipcRenderer.invoke("show-in-folder", p),
// 初始化
initDatabase: async () => {
const dataPath = await ipcRenderer.invoke("get-data-path");
db = new SimpleDB(dataPath);
return dataPath;
},
// 数据库操作
listDatabases: () => db ? db.listDatabases() : [],
createDatabase: (name) => { if (!db) throw new Error("未初始化"); return db.createDatabase(name); },
deleteDatabase: (name) => { if (!db) throw new Error("未初始化"); return db.deleteDatabase(name); },
renameDatabase: (o, n) => { if (!db) throw new Error("未初始化"); return db.renameDatabase(o, n); },
openDatabase: (name) => { if (!db) throw new Error("未初始化"); return db.openDatabase(name); },
getBackups: () => db ? db.getBackups() : [],
findByDateModified: (d) => db ? db.findByDateModified(d) : null,
upsertBackup: (info) => { if (!db) throw new Error("未初始化"); return db.upsertBackup(info); },
updateTag: (id, tag) => { if (!db) throw new Error("未初始化"); return db.updateTag(id, tag); },
deleteBackup: (id) => { if (!db) throw new Error("未初始化"); return db.deleteBackup(id); },
getCurrentDbName: () => db ? db.getCurrentDbName() : null,
getCurrentStoragePath: () => db ? db.getCurrentStoragePath() : null,
// 文件操作
getFileStats: (p) => {
try {
const s = fs.statSync(p);
return { size: s.size, mtime: s.mtime.toISOString(), isDirectory: s.isDirectory() };
} catch { return null; }
},
pathExists: (p) => fs.existsSync(p),
copyFile: (s, d) => { try { fs.copyFileSync(s, d); return true; } catch { return false; } },
deleteFile: (p) => { try { if (fs.existsSync(p)) fs.unlinkSync(p); return true; } catch { return false; } },
ensureDir,
// 压缩
compressFolder,
copyArchive,
isSupportedArchive,
getArchiveType,
// 工具
getLatestModifiedTime,
formatDateModified,
formatDateDisplay,
formatFileSize,
generateBackupFilename,
getBaseName,
isNameMatching,
pathJoin: (...args) => path.join(...args),
pathBasename: (p) => path.basename(p)
});