mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 03:32:54 -05:00
: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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
6
upcoming-release-notes/7069.md
Normal file
6
upcoming-release-notes/7069.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Enhancements
|
||||
authors: [MikesGlitch]
|
||||
---
|
||||
|
||||
Converted Electron backups to zip with metadata.json for easy importing
|
||||
Reference in New Issue
Block a user