:electron: Electron backups converted to zip for easy importing (#7069)

* e:electron: electron backups converted to zip for easy importing

* release notes

* fix lint

* suggestion from rabbit

* Change category from Maintenance to Enhancements
This commit is contained in:
Michael Clark
2026-03-04 18:00:07 +00:00
committed by GitHub
parent 3797cff716
commit b373b612a4
2 changed files with 41 additions and 13 deletions

View File

@@ -1,6 +1,7 @@
import type { Database } from '@jlongster/sql.js';
// @ts-strict-ignore
import AdmZip from 'adm-zip';
import * as dateFns from 'date-fns';
import { v4 as uuidv4 } from 'uuid';
import * as connection from '../../platform/server/connection';
import * as fs from '../../platform/server/fs';
@@ -26,7 +27,7 @@ async function getBackups(id: string): Promise<BackupWithDate[]> {
let paths = [];
if (await fs.exists(backupDir)) {
paths = await fs.listDir(backupDir);
paths = paths.filter(file => file.match(/\.sqlite$/));
paths = paths.filter(file => file.match(/\.zip$/));
}
const backups = await Promise.all(
@@ -112,20 +113,42 @@ export async function makeBackup(id: string) {
await fs.removeFile(fs.join(fs.getBudgetDir(id), LATEST_BACKUP_FILENAME));
}
const backupId = `${uuidv4()}.sqlite`;
const backupId = `${dateFns.format(new Date(), 'yyyy-MM-dd_HH-mm-ss')}.zip`;
const backupPath = fs.join(budgetDir, 'backups', backupId);
if (!(await fs.exists(fs.join(budgetDir, 'backups')))) {
await fs.mkdir(fs.join(budgetDir, 'backups'));
}
await fs.copyFile(fs.join(budgetDir, 'db.sqlite'), backupPath);
// Copy db to a temp path so we can clean CRDT messages before zipping
const tempDbPath = fs.join(
budgetDir,
'backups',
`db.${Date.now()}.sqlite.tmp`,
);
// Remove all the messages from the backup
const db = await sqlite.openDatabase(backupPath);
sqlite.runQuery(db, 'DELETE FROM messages_crdt');
sqlite.runQuery(db, 'DELETE FROM messages_clock');
sqlite.closeDatabase(db);
await fs.copyFile(fs.join(budgetDir, 'db.sqlite'), tempDbPath);
let db: Database | undefined;
try {
// Remove all the messages from the backup
db = await sqlite.openDatabase(tempDbPath);
sqlite.runQuery(db, 'DELETE FROM messages_crdt');
sqlite.runQuery(db, 'DELETE FROM messages_clock');
// Zip up the cleaned db and metadata into a single backup file
const zip = new AdmZip();
zip.addLocalFile(tempDbPath, '', 'db.sqlite');
zip.addLocalFile(fs.join(budgetDir, 'metadata.json'));
zip.writeZip(backupPath);
} finally {
if (db) {
sqlite.closeDatabase(db);
}
if (await fs.exists(tempDbPath)) {
await fs.removeFile(tempDbPath);
}
}
const toRemove = await updateBackups(await getBackups(id));
for (const id of toRemove) {
@@ -201,10 +224,9 @@ export async function loadBackup(id: string, backupId: string) {
prefs.unloadPrefs();
await fs.copyFile(
fs.join(budgetDir, 'backups', backupId),
fs.join(budgetDir, 'db.sqlite'),
);
const zip = new AdmZip(fs.join(budgetDir, 'backups', backupId));
zip.extractEntryTo('db.sqlite', budgetDir, false, true);
zip.extractEntryTo('metadata.json', budgetDir, false, true);
}
}

View File

@@ -0,0 +1,6 @@
---
category: Enhancements
authors: [MikesGlitch]
---
Converted Electron backups to zip with metadata.json for easy importing