Discard a cache whose list of what did not decrypt is damaged
check / check (push) Waiting to run

metadata.json's list of the albums and files the refresh could not
decrypt is read entry by entry, so a value that is not a list of records
discards the cache as other damage does, instead of failing every later
backup. "Backup layout" now says that an album, or a file's copy in an
album, that quak can no longer decrypt loses its folder, JSON or link as
a deleted one does, and the originals stay. It no longer mentions the
empty title in failures.json, which no test covers.

Model: opus-5-5
This commit is contained in:
2026-10-14 04:15:47 +00:00
parent 3f03689ae4
commit 25a01e2345
2 changed files with 20 additions and 12 deletions
+14 -11
View File
@@ -746,14 +746,14 @@ library's `lib.backup({ includeThumbnails: true })` also writes
`thumbnails/<fileID>.jpg` beside `collections/`; `quak backup` does not. `thumbnails/<fileID>.jpg` beside `collections/`; `quak backup` does not.
A file in the backup's scope that the library could not decrypt (see "Default A file in the backup's scope that the library could not decrypt (see "Default
reads vs. fresh reads") goes into `failures.json` with an empty title, since its reads vs. fresh reads") goes into `failures.json` with an error that starts
title is encrypted too, and an error that starts `could not be decrypted:` and `could not be decrypted:` and gives the reason. If a copy of it in another album
gives the reason. If a copy of it in another album decrypted, it does not: its decrypted, it does not: its original is stored from that copy, and the album
original is stored from that copy, and the album where it does not decrypt where it does not decrypt neither links nor lists it. An album the library could
neither links nor lists it. An album the library could not decrypt has no name not decrypt has no name quak can read and no files it can list. A run without
quak can read and no files it can list. A run without `--json` prints one line `--json` prints one line for it,
for it, `FAILED album <id>: could not be decrypted: <reason>`, and `--json` and `FAILED album <id>: could not be decrypted: <reason>`, and `--json` and the
the `BackupResult` list its ID in `undecryptableAlbums`. Such a file in `BackupResult` list its ID in `undecryptableAlbums`. Such a file in
`failures.json`, or such an album, keeps the exit code at 1, run after run, `failures.json`, or such an album, keeps the exit code at 1, run after run,
until the server sends a copy that decrypts or deletes it; the next run then until the server sends a copy that decrypts or deletes it; the next run then
drops it. drops it.
@@ -794,9 +794,12 @@ both, each named after the title with that file's extension (`IMG_0001.heic` and
Each run removes the symlinks into the date folders that no longer belong in Each run removes the symlinks into the date folders that no longer belong in
their collection's directory, and the directories (and JSON) of collections that their collection's directory, and the directories (and JSON) of collections that
were deleted or renamed. Nothing else in `collections/` is touched: a file or a were deleted or renamed. An album quak can no longer decrypt loses its directory
symlink you put there stays, and a directory that still holds one after its and JSON the same way, and a file whose copy in an album quak can no longer
symlinks are removed stays too, with its JSON. decrypt loses its symlink there and its entry in that album's JSON; the
originals stay at their save paths. Nothing else in `collections/` is touched: a
file or a symlink you put there stays, and a directory that still holds one
after its symlinks are removed stays too, with its JSON.
Each file is downloaded exactly once regardless of how many collections it Each file is downloaded exactly once regardless of how many collections it
appears in, and written once: straight to its save path, with no copy left in appears in, and written once: straight to its save path, with no copy left in
+6 -1
View File
@@ -119,7 +119,12 @@ export class MetadataStore {
}; };
store.files.set(fileKey(file.collectionID, file.id), file); store.files.set(fileKey(file.collectionID, file.id), file);
} }
store.undecryptable = parsed.undecryptable ?? []; for (const record of parsed.undecryptable) {
if (typeof record.collectionID !== "number") {
throw new Error("damaged record of what did not decrypt");
}
store.undecryptable.push(record);
}
} catch { } catch {
// Any corruption discards the partial result: a half-read cache is // Any corruption discards the partial result: a half-read cache is
// worse than an empty one, since the refresh unit will rebuild it. // worse than an empty one, since the refresh unit will rebuild it.