Rebuild backup on the library API with a durable failure ledger (closes #51)
check / check (push) Successful in 30s

backup() is a Library method: it refreshes, fetches each pending original
(and optional thumbnails) through the content cache and pools, then rebuilds
the sidecar, symlink, and per-collection JSON views from the model. The
downloadDirectory layout and exit-code contract are unchanged.

An original already on disk is complete and never re-fetched, so runs are
idempotent and resume after interruption. Per-file download and symlink
failures go to a durable failures.json and no longer abort the run
(subsuming #8); a file failing more than one way in a run counts one attempt
and is listed once.

Each run reconciles the ledger against the files it attempted, so a failure
for a since-deleted or out-of-scope file clears instead of failing every
future backup.

Model: opus-4-8
This commit is contained in:
2026-09-22 18:04:50 +00:00
parent 5db59a6e2b
commit 5f7b786dda
5 changed files with 818 additions and 482 deletions
+8 -3
View File
@@ -8,7 +8,7 @@ import { Command } from "commander";
import envPaths from "env-paths";
import { Client, type ClientSnapshot } from "../src/client.js";
import { init } from "../src/crypto/index.js";
import { runBackup } from "../src/backup.js";
import { Library } from "../src/library/index.js";
import { runMetadataBackup } from "../src/metadata-backup.js";
import {
listMissingThumbnails,
@@ -321,9 +321,14 @@ program
const client = requireSession();
stderr.write("Starting backup...\n");
const result = await runBackup(client, dir, (msg) => {
if (!opts.json) stderr.write(msg + "\n");
const lib = await Library.open({ client, downloadDirectory: dir });
const result = await lib.backup({
downloadDirectory: dir,
onProgress: (msg) => {
if (!opts.json) stderr.write(msg + "\n");
},
});
lib.close();
if (opts.json) {
stdout.write(JSON.stringify(result, null, 2) + "\n");