Back up from a copy that decrypted even outside a run's albums; README wording

A file whose copy in an album in scope does not decrypt is now backed up
from a copy that decrypted in any album, not only one in scope; with no
such copy it is still recorded as failed. The README now says the exit
code stays at 1 until the server sends a copy that decrypts or deletes it,
and that only a run without --json prints the FAILED album line.
StoredMetadata.undecryptable is required, like the other fields.

Model: opus-5-5
This commit is contained in:
2026-10-14 01:41:38 +00:00
parent 0a756be5de
commit c465496682
3 changed files with 23 additions and 20 deletions
+7 -6
View File
@@ -136,11 +136,12 @@ matters.
After a `1`, the next run fetches each missing original again, fetches the ML
data that is not cached, and rebuilds the album links. A file or album quak
could not decrypt keeps the exit code at `1` until the server's copy changes
(see "Backup layout"). An original that a `--verify` run could not read, or put
back with bytes that still do not match, stays at its save path. Only a later
`--verify` run checks it again: a run without `--verify` leaves it as it is and
takes the file out of `failures.json`, so that run can exit 0.
could not decrypt keeps the exit code at `1` until the server sends a copy that
decrypts, or deletes it (see "Backup layout"). An original that a `--verify` run
could not read, or put back with bytes that still do not match, stays at its
save path. Only a later `--verify` run checks it again: a run without `--verify`
leaves it as it is and takes the file out of `failures.json`, so that run can
exit 0.
## Examples
@@ -749,7 +750,7 @@ into `failures.json` with an empty title, since its title is encrypted too, and
an error that starts `could not be decrypted:` and gives the reason. A file in
two albums is backed up as usual if its copy in the other album decrypted. An
album the library could not decrypt has no name quak can read and no files it
can list. Each run prints one line for it,
can list. A run without `--json` prints one line for it,
`FAILED album <id>: could not be decrypted: <reason>`, and `--json` and the
`BackupResult` list its ID in `undecryptableAlbums`. Every other file is backed
up. Either kind keeps the exit code at 1, run after run, until the server sends
+15 -13
View File
@@ -609,9 +609,9 @@ const runLockedBackup = async (
// What the library could not decrypt. An album is logged by its ID, as
// its name is not known, so a run limited to some albums logs it too. A
// file in an album in scope is recorded as failed, with an empty title as
// its metadata is not known, unless a copy of it in another album
// decrypted.
// file in an album in scope is backed up from a copy of it in another
// album that decrypted, even an album out of scope. With no such copy it
// is recorded as failed, with an empty title as its metadata is not known.
const scope = new Set(collections.map((c) => c.id));
const undecryptableAlbums: number[] = [];
for (const record of lib.undecryptable()) {
@@ -619,16 +619,18 @@ const runLockedBackup = async (
if (record.fileID === undefined) {
log(`FAILED album ${record.collectionID}: ${error}`);
undecryptableAlbums.push(record.collectionID);
} else if (
scope.has(record.collectionID) &&
!memberships.has(record.fileID)
) {
recordFailure(
record.fileID,
"",
collectionName.get(record.collectionID) ?? "",
new Error(error),
);
} else if (scope.has(record.collectionID)) {
const copies = memberships.get(record.fileID);
if (copies) {
distinct.set(record.fileID, representative(copies));
} else {
recordFailure(
record.fileID,
"",
collectionName.get(record.collectionID) ?? "",
new Error(error),
);
}
}
}
+1 -1
View File
@@ -49,7 +49,7 @@ interface StoredMetadata {
collectionsSinceTime: Microseconds;
collections: StoredCollection[];
files: StoredFile[];
undecryptable?: Undecryptable[];
undecryptable: Undecryptable[];
}
const encodeKey = (key: Uint8Array): string =>