Compare commits

..
1 Commits
Author SHA1 Message Date
sneak 10fe0c9841 --verify keeps a mismatched original until a matching copy replaces it (closes #199)
check / check (push) Waiting to run
With --verify, an original whose hash does not match is no longer removed
before its replacement is downloaded. replaceOriginal, beside placeOriginal
in src/library/content.ts, has the content cache's new fetchOriginal write
the replacement to a temporary file beside the original, hashes it, and
renames it over the original only when it matches; a live photo's image and
video are both hashed before either is renamed. A failed or mismatching
download leaves the old bytes and album links, removes the temporary files,
and lands in failures.json. The replacement is always downloaded, never
copied from the cache. The leftover-file cleanup also takes a live photo's
replacement files, .quak-<pid>-<random>.<ext>.

Model: opus-5-5
2026-10-16 04:25:55 +00:00
2 changed files with 18 additions and 12 deletions
+11 -8
View File
@@ -887,14 +887,17 @@ own, hashed the same way, and renamed over the original only when it matches; a
live photo's image and video are both hashed before either is renamed. Until
then the original stays as it was: when the download fails, or what it brings
does not match either, the temporary files are deleted, the original keeps its
bytes and its album links, and the file goes into `failures.json`. A file whose
metadata records no hash is left as it is and counted as unchecked. A stored
original that cannot be read is left as it is and counts as failed. The summary
and `--json` add the counts `verified`, `mismatched` and `unchecked`, all of
originals that were already stored; one first downloaded in this run is in none
of them. A mismatch that was replaced with matching bytes does not make the exit
code non-zero. Without `--verify` nothing is hashed, the summary is unchanged,
and the three counts are 0 in `--json`.
bytes and its album links, and the file goes into `failures.json`. A run that
stops between renaming a live photo's new image and its new video leaves the new
image beside the old video, which a later `--verify` run replaces if the video
was the part that did not match. A file whose metadata records no hash is left
as it is and counted as unchecked. A stored original that cannot be read is left
as it is and counts as failed. The summary and `--json` add the counts
`verified`, `mismatched` and `unchecked`, all of originals that were already
stored; one first downloaded in this run is in none of them. A mismatch that was
replaced with matching bytes does not make the exit code non-zero. Without
`--verify` nothing is hashed, the summary is unchanged, and the three counts are
0 in `--json`.
Each original is written to a temporary file in the same directory, synced to
disk, and renamed into place, so an original is either complete or absent, even
+7 -4
View File
@@ -13,6 +13,8 @@
// originals, the thumbnail pool for thumbnails. The pool limits concurrency,
// orders on-demand work ahead of background, and dedups by key so a fileID
// requested twice while the first is still in flight downloads once.
// `fetchOriginal` passes no key, so it is not shared with a fetch of the same
// file already in flight.
//
// Integrity. The reused streaming decrypt is the enforced guarantee: every
// chunk is authenticated and the writer renames the file into place only once
@@ -439,10 +441,11 @@ export const placeOriginal = async (
// copy there, a live photo as its image and its video beside that file, each
// with its own extension. Only a copy that matches is renamed over the stored
// one, and a live photo's image and video are both hashed before either is
// renamed. Two renames cannot happen at once: should the video's fail, the
// image is already the new one, and the next backup with `verify` finds that
// the two do not match. When `get` fails or the copy does not match, the
// temporary files are removed and the stored original stays as it was.
// renamed. The image is renamed first: should the run stop, or the video's
// rename fail, before the video is renamed, the new image is left beside the
// old video, and a later backup with `verify` replaces the video if the video
// was the part that did not match. When `get` fails or the copy does not match,
// the temporary files are removed and the stored original stays as it was.
export const replaceOriginal = async (
root: string,
file: EnteFile,