Versie 0.2.0
This commit is contained in:
commit
e77d8fd373
495 files changed
+81026
No files matched your search
@@ -0,0 +1,127 @@
|
||||
// Generated, do not edit. sha256:e1360b8b92e9fdeccd0b88044df16a08eafd2bf7c084bf7549eeac1712ff09fb
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import { writeFile, rename, rm, access } from 'node:fs/promises';
|
||||
import { CONFIG } from '../shared/config.js';
|
||||
import { BASELINE_VERSION } from './db/migrations.js';
|
||||
import { closeDatabase } from './db/conn.mjs';
|
||||
import { initDatabase, dbFile } from './db.js';
|
||||
import { fail } from '../shared/app-error.js';
|
||||
|
||||
const BUREN = ['-wal', '-shm'];
|
||||
|
||||
const VERGRENDELD = new Set(['EBUSY', 'EPERM', 'EACCES']);
|
||||
const WACHT_MS = [50, 150, 400];
|
||||
|
||||
async function hernoemVolhardend(van, naar) {
|
||||
for (let poging = 0; ; poging++) {
|
||||
try { return await rename(van, naar); }
|
||||
catch (e) {
|
||||
if (!VERGRENDELD.has(e.code) || poging >= WACHT_MS.length) throw vertaalVergrendeling(e, van);
|
||||
await new Promise(r => setTimeout(r, WACHT_MS[poging]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function vertaalVergrendeling(e, pad) {
|
||||
if (!VERGRENDELD.has(e.code)) return e;
|
||||
return new Error(
|
||||
`Het databasebestand is door een ander programma in gebruik (${e.code}), dus het kon niet worden `
|
||||
+ `verplaatst: ${pad}. Er is niets gewijzigd. Sluit wat er nog aan deze datamap hangt en probeer het `
|
||||
+ `opnieuw: een tweede exemplaar van de app, de MCP-server (npm run mcp), of een database-viewer.`);
|
||||
}
|
||||
|
||||
const bestaat = async pad => { try { await access(pad); return true; } catch { return false; } };
|
||||
|
||||
export function keurBestand(pad) {
|
||||
let db;
|
||||
try {
|
||||
db = new DatabaseSync(pad, { readOnly: true });
|
||||
} catch (e) {
|
||||
fail('dbfile.notReadableSqlite', { reden: e.message }, `This is not a readable SQLite file (${e.message}).`);
|
||||
}
|
||||
try {
|
||||
const heeftSchema = db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='Schema'`).get();
|
||||
if (!heeftSchema) {
|
||||
fail('dbfile.noSchemaTable', null,
|
||||
'This file has no Schema table; a database from before the baseline can no longer be converted '
|
||||
+ '(git log --all -- scripts/baseline-convert.mjs).');
|
||||
}
|
||||
const rij = db.prepare(`SELECT version FROM Schema`).get();
|
||||
const versie = rij && Number(rij.version);
|
||||
if (!Number.isInteger(versie)) fail('dbfile.noReadableVersion', null, 'The Schema table holds no readable version.');
|
||||
if (versie < BASELINE_VERSION) {
|
||||
fail('dbfile.versionBeforeBaseline', { versie, baseline: BASELINE_VERSION },
|
||||
`This database is version ${versie}, from before the new layout (version ${BASELINE_VERSION}); `
|
||||
+ 'see git log --all -- scripts/baseline-convert.mjs.');
|
||||
}
|
||||
if (versie > CONFIG.SCHEMA_VERSION) {
|
||||
fail('dbfile.versionNewerThanApp', { versie, app: CONFIG.SCHEMA_VERSION },
|
||||
`This database is version ${versie} and thus newer than the app (${CONFIG.SCHEMA_VERSION}).`);
|
||||
}
|
||||
const wezen = db.prepare(`PRAGMA foreign_key_check`).all();
|
||||
return { versie, wezen: wezen.length };
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
export async function restoreDatabase(bytes) {
|
||||
if (!bytes || bytes.length === 0) fail('dbfile.emptyFile', null, 'Received an empty file.');
|
||||
if (bytes.subarray(0, 15).toString('latin1') !== 'SQLite format 3') {
|
||||
fail('dbfile.notSqlite', null, 'This is not a SQLite file (the file header does not match).');
|
||||
}
|
||||
|
||||
const doel = dbFile();
|
||||
const incoming = `${doel}.new`;
|
||||
const previous = `${doel}.previous`;
|
||||
await writeFile(incoming, bytes, { mode: 0o600 });
|
||||
|
||||
let keuring;
|
||||
try {
|
||||
keuring = keurBestand(incoming);
|
||||
} catch (e) {
|
||||
await rm(incoming, { force: true });
|
||||
throw e;
|
||||
}
|
||||
|
||||
await wisselDatabaseBestand(incoming, previous);
|
||||
|
||||
const regel = initDatabase();
|
||||
return {
|
||||
versie: keuring.versie,
|
||||
wezen: keuring.wezen,
|
||||
previous,
|
||||
regel,
|
||||
geheimenWaarschuwing: 'De geheimen (NWC-URI\'s, LND-macaroons, provider-sleutels) staan in '
|
||||
+ 'secrets.json in de map van deze portefeuille en zijn NIET meeverhuisd. Komt deze database uit een andere administratie, '
|
||||
+ 'controleer dan je tracking-bronnen: een geheim hangt aan een bron-id en dat id kan nu bij een '
|
||||
+ 'andere bron horen.',
|
||||
};
|
||||
}
|
||||
|
||||
export async function wisselDatabaseBestand(incoming, previous, naWissel = async () => {}) {
|
||||
let opzijGezet = false;
|
||||
const doel = dbFile();
|
||||
closeDatabase();
|
||||
try {
|
||||
await rm(previous, { force: true });
|
||||
for (const b of BUREN) await rm(previous + b, { force: true });
|
||||
if (await bestaat(doel)) {
|
||||
await hernoemVolhardend(doel, previous);
|
||||
opzijGezet = true;
|
||||
for (const b of BUREN) if (await bestaat(doel + b)) await hernoemVolhardend(doel + b, previous + b);
|
||||
}
|
||||
await hernoemVolhardend(incoming, doel);
|
||||
await naWissel();
|
||||
} catch (e) {
|
||||
if (opzijGezet && !(await bestaat(doel))) {
|
||||
try {
|
||||
await rename(previous, doel);
|
||||
for (const b of BUREN) if (await bestaat(previous + b)) await rename(previous + b, doel + b);
|
||||
} catch { }
|
||||
}
|
||||
await rm(incoming, { force: true });
|
||||
try { initDatabase(); } catch { }
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user