a ledger of runs, and what is still outstanding
Two things the panel could not answer. A run's state lives in a Map, so a restart erased it — and with it any record that a file FAILED. That is the gap the manifests structurally cannot cover: a derived folder records the size, mtime and variants it was built from, so provenance is already precise, but a file that did not build has no folder and leaves no trace. Once the watcher is running, a failure at three in the morning would never be discovered. So one JSON line per run, appended to media/.sync-log.jsonl. A file rather than a table because db/schema.ts is emphatic that the connection is the only thing this plugin stores, and a ledger is losable — re-run and it rebuilds. Beside derived/ rather than inside it, so wiping derived does not erase the history of what happened before, which is the comparison it exists for. Written in a finally, so a run that died still records that it did. And the line above the button now says what is TRUE rather than what a run once did: reconcile() walks originals, reads each manifest, and reports current, missing and changed. It uses upToDate — the same predicate the sync uses to skip a file — because two definitions of "current" is how a panel comes to say everything is in step while the next run rebuilds half of it. Missing and changed are separated because only the second means somebody edited something. The ledger is not polled. It reads the whole tree, so it loads on arrival and again when a run stops — the only two moments its answer can change. Sync also takes an optional folder now, so a subtree can be re-synced on its own rather than the whole library. The scope is recorded on each run, because "3 built" means different things for one folder and for everything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+28
-1
@@ -6,6 +6,8 @@ import { readSettings, saveRootPath } from './settings';
|
||||
import { readWebsiteStatus, cloneWebsite, updateWebsite } from './website';
|
||||
import { readMediaStatus, createMediaDir } from './media';
|
||||
import { readSync, startSync } from './sync';
|
||||
import { readRuns } from './ledger';
|
||||
import { reconcile } from './derive';
|
||||
import { fofosRequest, FofosError, probe, type FofosResponse } from './fofos-client';
|
||||
|
||||
/**
|
||||
@@ -243,7 +245,14 @@ router.post('/_config/media/create', async (ctx) => {
|
||||
*/
|
||||
router.post('/_config/media/sync', async (ctx) => {
|
||||
const user = ctx.get('user');
|
||||
return ctx.json(startSync(user.email));
|
||||
// A folder inside `originals/`, or nothing for all of it. Named `path` because that is what the file
|
||||
// browser calls it, and this is the value it will hand over when a folder is synced from there.
|
||||
const { path } = (ctx.get('body') ?? {}) as { path?: string };
|
||||
try {
|
||||
return ctx.json(startSync(user.email, { scope: path }));
|
||||
} catch (error) {
|
||||
return ctx.json({ error: (error as Error).message }, 400);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/_config/media/sync', async (ctx) => {
|
||||
@@ -251,6 +260,24 @@ router.get('/_config/media/sync', async (ctx) => {
|
||||
return ctx.json(readSync(user.email));
|
||||
});
|
||||
|
||||
/**
|
||||
* What has been done, and what is left.
|
||||
*
|
||||
* Separate from the live job state on purpose: that one is polled every second while a run is in
|
||||
* flight, and this one walks the whole tree reading manifests. Answering both from one route would
|
||||
* make every tick of a progress bar re-read the library.
|
||||
*/
|
||||
router.get('/_config/media/ledger', async (ctx) => {
|
||||
const user = ctx.get('user');
|
||||
const scope = ctx.req.query('path') ?? '';
|
||||
try {
|
||||
const [runs, state] = await Promise.all([readRuns(user.email), reconcile(user.email, scope)]);
|
||||
return ctx.json({ runs, reconciliation: state });
|
||||
} catch (error) {
|
||||
return ctx.json({ error: (error as Error).message }, 400);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Is this plugin usable? Asked once by the detail panel rather than by all nine sections.
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user