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) });