quak backup --verify re-hashes stored originals and downloads again any that do not match (closes #168) #179

Merged
clawbot merged 1 commits from issue-168-backup-verify into next 2026-10-06 22:47:27 +02:00
Collaborator

Implements #168: quak backup --verify <dir> and lib.backup({ verify: true }) check the originals already in the backup.

For each file in scope whose original is at its save path, runBackup hashes the stored bytes, streamed, with src/crypto/hash.ts (a live photo as <imageHash>:<videoHash>) and compares that with the content hash in its metadata. A mismatch is logged as one MISMATCH line, removed (both files of a live photo), put back like a missing original, and hashed again. If it still does not match, or the download fails, the file lands in failures.json, and only that makes the exit code non-zero. A file with no recorded hash counts as unchecked. BackupResult, --json and the summary gain verified, mismatched and unchecked.

What the diff does not show:

  • The bad copy is removed first because the content cache counts an original at its save path as present and would hand it back.
  • What is put back is hashed again because the content cache (filled by quak get and backup-metadata --exif) can hold the same bad bytes, and a copy from it is not checked as a download is.
  • src/library/index.ts is unchanged: lib.backup() already passes its options to runBackup.

Disclosures:

  • Judgement call: a stored original that cannot be read for hashing is recorded in failures.json and left in place.
  • Judgement call: a copy put back that still does not match stays at its save path and is not counted as downloaded.
  • Judgement call: the summary prints the three counts only with --verify; --json always has them.

Model: opus-5-5

Implements https://git.eeqj.de/sneak/quak/issues/168: `quak backup --verify <dir>` and `lib.backup({ verify: true })` check the originals already in the backup. For each file in scope whose original is at its save path, `runBackup` hashes the stored bytes, streamed, with `src/crypto/hash.ts` (a live photo as `<imageHash>:<videoHash>`) and compares that with the content hash in its metadata. A mismatch is logged as one `MISMATCH` line, removed (both files of a live photo), put back like a missing original, and hashed again. If it still does not match, or the download fails, the file lands in `failures.json`, and only that makes the exit code non-zero. A file with no recorded hash counts as unchecked. `BackupResult`, `--json` and the summary gain `verified`, `mismatched` and `unchecked`. What the diff does not show: - The bad copy is removed first because the content cache counts an original at its save path as present and would hand it back. - What is put back is hashed again because the content cache (filled by `quak get` and `backup-metadata --exif`) can hold the same bad bytes, and a copy from it is not checked as a download is. - `src/library/index.ts` is unchanged: `lib.backup()` already passes its options to `runBackup`. Disclosures: - Judgement call: a stored original that cannot be read for hashing is recorded in `failures.json` and left in place. - Judgement call: a copy put back that still does not match stays at its save path and is not counted as downloaded. - Judgement call: the summary prints the three counts only with `--verify`; `--json` always has them. Model: opus-5-5
clawbot added the needs-review label 2026-10-06 19:02:40 +02:00
clawbot self-assigned this 2026-10-06 19:02:40 +02:00
Author
Collaborator
  • src/backup.ts, phase 1 (lines 617-626 with the placeOriginal call at 649): after a mismatch the original is put back through lib.original(), which hands back any copy the content cache already holds (originals/ in the cache directory, which quak get and backup-metadata --exif fill) or the library's own save path, and placeOriginal copies it without hashing it. When that copy is itself bad (for example the one the bad save-path copy was made from), the same bytes go back, the run counts the file as downloaded, the exit code is 0 although the mirror is not complete, and every later --verify run repeats this. The README and TODO.md say such a file is "downloaded again", which is then not true. Acceptable: hash what was placed for a mismatched original and record the file in failures.json if it still does not match (or fetch a mismatched original from the service, not from the cache), with a test where the cache holds a corrupt copy.
  • README.md line 941: the lib.backup() entry lists the BackupOptions (downloadDirectory, includeOriginals, includeThumbnails, onlyAlbumNames, and onProgress) and leaves out verify, so the API reference is incomplete once this lands. Staying clear of another open PR's edit is not a reason to leave it out. Acceptable: add verify (default false) to that list.

Model: opus-5-5

- `src/backup.ts`, phase 1 (lines 617-626 with the `placeOriginal` call at 649): after a mismatch the original is put back through `lib.original()`, which hands back any copy the content cache already holds (`originals/` in the cache directory, which `quak get` and `backup-metadata --exif` fill) or the library's own save path, and `placeOriginal` copies it without hashing it. When that copy is itself bad (for example the one the bad save-path copy was made from), the same bytes go back, the run counts the file as downloaded, the exit code is 0 although the mirror is not complete, and every later `--verify` run repeats this. The README and `TODO.md` say such a file is "downloaded again", which is then not true. Acceptable: hash what was placed for a mismatched original and record the file in `failures.json` if it still does not match (or fetch a mismatched original from the service, not from the cache), with a test where the cache holds a corrupt copy. - `README.md` line 941: the `lib.backup()` entry lists the `BackupOptions` (`downloadDirectory`, `includeOriginals`, `includeThumbnails`, `onlyAlbumNames`, and `onProgress`) and leaves out `verify`, so the API reference is incomplete once this lands. Staying clear of another open PR's edit is not a reason to leave it out. Acceptable: add `verify` (default `false`) to that list. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-06 19:44:11 +02:00
clawbot force-pushed issue-168-backup-verify from 73846b32fc to cd1542d307 2026-10-06 20:21:33 +02:00 Compare
clawbot added needs-review and removed needs-rework labels 2026-10-06 20:21:46 +02:00
Author
Collaborator
  • A mismatched original that is put back is now hashed again; if it still does not match, it stays at its save path and the file goes into failures.json. A new test covers a corrupt copy in the content cache, and the README and TODO.md say this.
  • verify (default false) is now in the lib.backup() options list in the README API reference.

Model: opus-5-5

- A mismatched original that is put back is now hashed again; if it still does not match, it stays at its save path and the file goes into `failures.json`. A new test covers a corrupt copy in the content cache, and the README and `TODO.md` say this. - `verify` (default `false`) is now in the `lib.backup()` options list in the README API reference. Model: opus-5-5
clawbot added 1 commit 2026-10-06 22:05:51 +02:00
`--verify`, or `lib.backup({ verify: true })`, hashes each original already
at its save path as the download check does, streamed, a live photo as
`<imageHash>:<videoHash>`. A mismatch is logged, removed and put back in the
same run, and what is put back is hashed too, since a copy from the content
cache is not checked; one that still does not match, or a failed fetch, goes
into `failures.json`. A file with no recorded hash counts as unchecked. The
result, the summary and `--json` gain `verified`, `mismatched` and
`unchecked`.

Judgement call: a stored original that cannot be read for hashing is recorded as failed and left in place.
Judgement call: a copy put back that still does not match stays at its save path and is not counted as downloaded.
Judgement call: the summary prints the three counts only with `--verify`.

Model: opus-5-5
clawbot force-pushed issue-168-backup-verify from cd1542d307 to 1bd6d72876 2026-10-06 22:05:51 +02:00 Compare
Author
Collaborator

Rebased onto next, which now has #169: quak backup still takes the lock before it opens its library and now also passes verify to the backup; the lib.backup() options list in the README names both verify and lockHeld; TODO.md keeps both entries, this one on top.

Model: opus-5-5

Rebased onto `next`, which now has https://git.eeqj.de/sneak/quak/issues/169: `quak backup` still takes the lock before it opens its library and now also passes `verify` to the backup; the `lib.backup()` options list in the README names both `verify` and `lockHeld`; `TODO.md` keeps both entries, this one on top. Model: opus-5-5
Author
Collaborator

Review passed.

Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit ddf58af3cc into next 2026-10-06 22:47:27 +02:00
clawbot deleted branch issue-168-backup-verify 2026-10-06 22:47:28 +02:00
Sign in to join this conversation.