feat(download): durable atomic writer and per-chunk progress hook (closes #39) #56

Merged
clawbot merged 1 commits from issue-39-atomic-fsync-progress into next 2026-09-22 12:01:06 +02:00
Collaborator

Foundation unit for the cache/API design (#36).

What changed:

  • writeAtomic now fsyncs the staged temp file before the rename and fsyncs the
    containing directory after it, then is exported so the metadata store can reuse
    the same durable write. It previously wrote and renamed with neither fsync, so a
    crash just after the write could leave an empty renamed file.
  • downloadFile and downloadThumbnail gain an optional onProgress(bytesDone)
    argument, fired within streamDecrypt as decrypted plaintext accumulates.
    Values are non-decreasing and the last equals bytesWritten; omitted, it is a
    no-op.

Unchanged on purpose: whole-file buffering (streaming-to-disk is a later unit),
retry semantics, and the TAG_FINAL truncation checks.

What the diff does not show:

  • Progress is per attempt. A retry restarts the file from byte zero (the pull
    state is not resumable), so a fresh attempt counts up from zero again; the
    monotonic/final-value contract holds within one successful download.
  • The directory fsync is after the rename deliberately: syncing it earlier would
    not make the new directory entry durable.

Scope vs #22: this closes only that issue's
durability gap (its area 1). Orphan .quak-*.tmp reaping, the symlink/mode docs,
and its remaining test coverage are left for a later unit, so it is
cross-referenced, not closed.

Model: opus-4-8

Foundation unit for the cache/API design (https://git.eeqj.de/sneak/quak/issues/36). What changed: - `writeAtomic` now fsyncs the staged temp file before the rename and fsyncs the containing directory after it, then is exported so the metadata store can reuse the same durable write. It previously wrote and renamed with neither fsync, so a crash just after the write could leave an empty renamed file. - `downloadFile` and `downloadThumbnail` gain an optional `onProgress(bytesDone)` argument, fired within `streamDecrypt` as decrypted plaintext accumulates. Values are non-decreasing and the last equals `bytesWritten`; omitted, it is a no-op. Unchanged on purpose: whole-file buffering (streaming-to-disk is a later unit), retry semantics, and the TAG_FINAL truncation checks. What the diff does not show: - Progress is per attempt. A retry restarts the file from byte zero (the pull state is not resumable), so a fresh attempt counts up from zero again; the monotonic/final-value contract holds within one successful download. - The directory fsync is after the rename deliberately: syncing it earlier would not make the new directory entry durable. Scope vs https://git.eeqj.de/sneak/quak/issues/22: this closes only that issue's durability gap (its area 1). Orphan `.quak-*.tmp` reaping, the symlink/mode docs, and its remaining test coverage are left for a later unit, so it is cross-referenced, not closed. Model: opus-4-8
clawbot added the needs-review label 2026-09-22 11:36:16 +02:00
clawbot self-assigned this 2026-09-22 11:36:16 +02:00
clawbot added 1 commit 2026-09-22 11:36:16 +02:00
Add fsync to the atomic writer and export it, and give the download
functions an optional per-chunk progress hook.

writeAtomic now fsyncs the staged temp file before the rename and fsyncs
the containing directory after it, so a power cut immediately after the
write cannot resurrect an empty renamed file. It is exported so the
metadata store can reuse the same durable write.

downloadFile and downloadThumbnail take an optional onProgress(bytesDone)
callback that fires within streamDecrypt as decrypted plaintext
accumulates; its values are non-decreasing and the last equals
bytesWritten. Absent, the callback is a no-op. Whole-file buffering,
retry semantics, and the TAG_FINAL truncation checks are unchanged.

Covers the fsync durability gap of #22 (area 1) only; orphan reaping,
symlink/mode docs, and the remaining test coverage there stay for a
later unit, so #22 is referenced, not closed.

Model: opus-4-8
Author
Collaborator

PASS — the atomic writer fsyncs the staged temp file before the rename and fsyncs the containing directory after, keeps the best-effort temp cleanup, and is exported; downloadFile/downloadThumbnail gain an optional per-chunk onProgress hook whose values are non-decreasing and whose last value equals bytesWritten, a no-op when absent; retry semantics and the TAG_FINAL truncation checks are unchanged; the added tests fail under mutation; and make check is green on the current next.

Model: opus-4-8

PASS — the atomic writer fsyncs the staged temp file before the rename and fsyncs the containing directory after, keeps the best-effort temp cleanup, and is exported; `downloadFile`/`downloadThumbnail` gain an optional per-chunk `onProgress` hook whose values are non-decreasing and whose last value equals `bytesWritten`, a no-op when absent; retry semantics and the `TAG_FINAL` truncation checks are unchanged; the added tests fail under mutation; and `make check` is green on the current `next`. Model: opus-4-8
clawbot merged commit 8f575550af into next 2026-09-22 12:01:06 +02:00
clawbot deleted branch issue-39-atomic-fsync-progress 2026-09-22 12:01:07 +02:00
Sign in to join this conversation.