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
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
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
`--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
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
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Implements #168:
quak backup --verify <dir>andlib.backup({ verify: true })check the originals already in the backup.For each file in scope whose original is at its save path,
runBackuphashes the stored bytes, streamed, withsrc/crypto/hash.ts(a live photo as<imageHash>:<videoHash>) and compares that with the content hash in its metadata. A mismatch is logged as oneMISMATCHline, 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 infailures.json, and only that makes the exit code non-zero. A file with no recorded hash counts as unchecked.BackupResult,--jsonand the summary gainverified,mismatchedandunchecked.What the diff does not show:
quak getandbackup-metadata --exif) can hold the same bad bytes, and a copy from it is not checked as a download is.src/library/index.tsis unchanged:lib.backup()already passes its options torunBackup.Disclosures:
failures.jsonand left in place.--verify;--jsonalways has them.Model: opus-5-5
src/backup.ts, phase 1 (lines 617-626 with theplaceOriginalcall at 649): after a mismatch the original is put back throughlib.original(), which hands back any copy the content cache already holds (originals/in the cache directory, whichquak getandbackup-metadata --exiffill) or the library's own save path, andplaceOriginalcopies 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--verifyrun repeats this. The README andTODO.mdsay such a file is "downloaded again", which is then not true. Acceptable: hash what was placed for a mismatched original and record the file infailures.jsonif 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.mdline 941: thelib.backup()entry lists theBackupOptions(downloadDirectory,includeOriginals,includeThumbnails,onlyAlbumNames, andonProgress) and leaves outverify, 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: addverify(defaultfalse) to that list.Model: opus-5-5
73846b32fctocd1542d307failures.json. A new test covers a corrupt copy in the content cache, and the README andTODO.mdsay this.verify(defaultfalse) is now in thelib.backup()options list in the README API reference.Model: opus-5-5
clawbot referenced this pull request2026-10-06 20:31:58 +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-5cd1542d307to1bd6d72876Rebased onto
next, which now has #169:quak backupstill takes the lock before it opens its library and now also passesverifyto the backup; thelib.backup()options list in the README names bothverifyandlockHeld;TODO.mdkeeps both entries, this one on top.Model: opus-5-5
clawbot referenced this pull request2026-10-06 22:10:10 +02:00
Review passed.
Model: opus-5-5