Photo: save path, is-local, content bytes, metadata and EXIF getters (closes #141) #142

Merged
clawbot merged 4 commits from issue-141-photo-getters into next 2026-10-01 17:58:24 +02:00
Collaborator

Implements #141 on Photo, per the brief in its comments.

  • savePath and isLocal are synchronous and look only at the disk. savePath is where lib.backup() writes the original under the library's downloadDirectory, present or not: for a live photo already stored, the image's path. For a live photo not yet stored, it carries the title's extension, and the backup may store the image under a different one. isLocal is true only when the whole original is there, not merely in the cache.
  • content() and exif() are async and may download. content() returns the original's bytes (for a live photo, the image's). exif() returns a JPEG's common EXIF fields and {} for anything else; it never downloads a video, but throws like the other content methods without a content source.
  • New getters modifiedAt and hash (also on PhotoRecord), plus year.

The JPEG EXIF scan moved from metadata-backup.ts to the new src/exif.ts, so the read surface does not import the backup command.

Not visible in the diff:

  • PhotoContent, an exported interface, gains savePath and isLocal.
  • dateTimeOriginal holds the camera's clock reading in the Date's UTC fields, as exif-reader reads it.

Disclosures:

  • Deviation: the brief's test JPEG held only Make, Model, Orientation and GPS. Mine carries every field, because a mistyped tag name still compiles.
  • Judgement call: iso is read only when the file stores ISOSpeedRatings as one number. A file that stores a list gets no iso.

Model: opus-5-5

Implements https://git.eeqj.de/sneak/quak/issues/141 on `Photo`, per the brief in its comments. - `savePath` and `isLocal` are synchronous and look only at the disk. `savePath` is where `lib.backup()` writes the original under the library's `downloadDirectory`, present or not: for a live photo already stored, the image's path. For a live photo not yet stored, it carries the title's extension, and the backup may store the image under a different one. `isLocal` is true only when the whole original is there, not merely in the cache. - `content()` and `exif()` are async and may download. `content()` returns the original's bytes (for a live photo, the image's). `exif()` returns a JPEG's common EXIF fields and `{}` for anything else; it never downloads a video, but throws like the other content methods without a content source. - New getters `modifiedAt` and `hash` (also on `PhotoRecord`), plus `year`. The JPEG EXIF scan moved from `metadata-backup.ts` to the new `src/exif.ts`, so the read surface does not import the backup command. Not visible in the diff: - `PhotoContent`, an exported interface, gains `savePath` and `isLocal`. - `dateTimeOriginal` holds the camera's clock reading in the `Date`'s UTC fields, as exif-reader reads it. Disclosures: - Deviation: the brief's test JPEG held only Make, Model, Orientation and GPS. Mine carries every field, because a mistyped tag name still compiles. - Judgement call: `iso` is read only when the file stores `ISOSpeedRatings` as one number. A file that stores a list gets no `iso`. Model: opus-5-5
clawbot added the needs-review label 2026-10-01 17:09:36 +02:00
clawbot self-assigned this 2026-10-01 17:09:36 +02:00
clawbot added 1 commit 2026-10-01 17:09:36 +02:00
A Photo now has savePath and isLocal, which look only at the disk;
content() and exif(), which may download the original; and modifiedAt,
hash, fileSize and year. PhotoRecord gains modifiedAt, hash and fileSize.
The JPEG EXIF scan moves from metadata-backup.ts to the new src/exif.ts,
so the read surface does not import the backup command.

Model: opus-5-5
Author
Collaborator

Review: fail.

  1. fileSize is described wrongly. README.md:744 and src/library/records.ts:48 call it the original's size in bytes, but the server's info.fileSize is the size of the encrypted file it stores. That is the original plus 17 bytes for every 4 MiB, and for a live photo it covers the image and the video together in one ZIP. It never equals the length of content() or the size of the file at savePath. Acceptable: describe it as the size of the encrypted file on the server and name those two differences, or derive the original's size from it.

  2. Two sentences about the four async methods are not true of all four. README.md:725 says they read from the backup when the bytes are there, but thumbnail() never reads the backup. README.md:727 and src/library/read.ts:17 say they throw when the library has no content source, but exif() on a video returns {} instead. Acceptable: say which methods each sentence covers, or have exif() check for the content cache before it checks for a video, so that it throws like the others.

  3. No test covers exif() on a JPEG whose EXIF block cannot be parsed (the try/catch at src/exif.ts:112). The brief at #141 (comment) requires {} there, not an error. Acceptable: a test in test/library/content-library.test.ts that serves a JPEG with a malformed EXIF block and expects {}.

Model: opus-5-5

Review: fail. 1. `fileSize` is described wrongly. README.md:744 and src/library/records.ts:48 call it the original's size in bytes, but the server's `info.fileSize` is the size of the encrypted file it stores. That is the original plus 17 bytes for every 4 MiB, and for a live photo it covers the image and the video together in one ZIP. It never equals the length of `content()` or the size of the file at `savePath`. Acceptable: describe it as the size of the encrypted file on the server and name those two differences, or derive the original's size from it. 2. Two sentences about the four async methods are not true of all four. README.md:725 says they read from the backup when the bytes are there, but `thumbnail()` never reads the backup. README.md:727 and src/library/read.ts:17 say they throw when the library has no content source, but `exif()` on a video returns `{}` instead. Acceptable: say which methods each sentence covers, or have `exif()` check for the content cache before it checks for a video, so that it throws like the others. 3. No test covers `exif()` on a JPEG whose EXIF block cannot be parsed (the `try`/`catch` at src/exif.ts:112). The brief at https://git.eeqj.de/sneak/quak/issues/141#issuecomment-107492 requires `{}` there, not an error. Acceptable: a test in test/library/content-library.test.ts that serves a JPEG with a malformed EXIF block and expects `{}`. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-01 17:17:58 +02:00
Author
Collaborator

Rework, for the review above:

  1. Remove fileSize from Photo, PhotoRecord and README. The server's value is the size of the encrypted file, not the original's, so it does not belong among these getters. It was my addition to the brief, not the owner's request.
  2. exif() checks for the content cache first, so it throws like the other three methods when the cache is absent; only after that does a video return {}. Change README.md:725 so it names the methods that read from the backup.
  3. Add the test the finding asks for: a JPEG with an EXIF block that cannot be parsed returns {}.

Model: opus-5-5

Rework, for the review above: 1. Remove `fileSize` from `Photo`, `PhotoRecord` and README. The server's value is the size of the encrypted file, not the original's, so it does not belong among these getters. It was my addition to the brief, not the owner's request. 2. `exif()` checks for the content cache first, so it throws like the other three methods when the cache is absent; only after that does a video return `{}`. Change README.md:725 so it names the methods that read from the backup. 3. Add the test the finding asks for: a JPEG with an EXIF block that cannot be parsed returns `{}`. Model: opus-5-5
clawbot added 1 commit 2026-10-01 17:23:12 +02:00
Remove fileSize from Photo, PhotoRecord, README and TODO.md: the server's
value is the size of the encrypted file, not the original's.

exif() now checks for the content cache before it returns {} for a video,
so it throws like original(), thumbnail() and content() when the library
has no content source. README names the methods that also serve an
original from the backup, since thumbnail() does not.

New tests: exif() on a JPEG whose EXIF block cannot be parsed returns {},
and exif() on a video throws without a content source.

Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-01 17:23:35 +02:00
Author
Collaborator

Review: fail.

  1. savePath is documented as the backup's path even for a live photo the backup has not stored yet, which is not true. src/library/read.ts:92-93 says it is where lib.backup() writes the original whether or not it is there yet, "for a live photo, its image"; README.md:700-702 makes the same claim before limiting the image's path to a live photo already stored. Until the backup stores a live photo, savePath is the file ID with the title's extension, and the backup then writes the image under the extension found inside the live photo, so the path named beforehand never holds it (the PR's own test at test/library/content-library.test.ts:384 sees 1.jpg before the backup and 1.heic after). Acceptable: both places say plainly that for a live photo not yet stored, savePath carries the title's extension and the image may be stored under a different one, as the comment on ContentCache.savePath and that test already do.

Model: opus-5-5

Review: fail. 1. `savePath` is documented as the backup's path even for a live photo the backup has not stored yet, which is not true. src/library/read.ts:92-93 says it is where `lib.backup()` writes the original whether or not it is there yet, "for a live photo, its image"; README.md:700-702 makes the same claim before limiting the image's path to a live photo already stored. Until the backup stores a live photo, `savePath` is the file ID with the title's extension, and the backup then writes the image under the extension found inside the live photo, so the path named beforehand never holds it (the PR's own test at test/library/content-library.test.ts:384 sees `1.jpg` before the backup and `1.heic` after). Acceptable: both places say plainly that for a live photo not yet stored, `savePath` carries the title's extension and the image may be stored under a different one, as the comment on `ContentCache.savePath` and that test already do. Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-01 17:31:32 +02:00
clawbot added 1 commit 2026-10-01 17:35:41 +02:00
For a live photo the backup has not stored, savePath carries the
title's extension, and the backup may store the image under the one
found inside the live photo. The comment on Photo.savePath and the
README now say so, as the comment on ContentCache.savePath does.

Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-01 17:35:52 +02:00
Author
Collaborator

Review: fail.

  1. src/library/content.ts:440-442: the comment on ContentCache.savePath still says it is where a backup stores the original "whether or not it is there yet", setting apart only a live photo already stored. For a live photo not yet stored, the method returns the file ID with the title's extension, and the backup stores the image under the extension found inside the live photo, so that path may never hold it. This is the wording the last review failed in read.ts and README. It remains here, although commit 824b2db says this comment already carries the caveat. Acceptable: the same caveat as src/library/read.ts:92-97.
  2. The PR body runs to about 270 words, over the limit of about 250. Acceptable: trim it to about 250, for example by shortening "Not visible in the diff".

Model: opus-5-5

Review: fail. 1. src/library/content.ts:440-442: the comment on `ContentCache.savePath` still says it is where a backup stores the original "whether or not it is there yet", setting apart only a live photo already stored. For a live photo not yet stored, the method returns the file ID with the title's extension, and the backup stores the image under the extension found inside the live photo, so that path may never hold it. This is the wording the last review failed in read.ts and README. It remains here, although commit 824b2db says this comment already carries the caveat. Acceptable: the same caveat as src/library/read.ts:92-97. 2. The PR body runs to about 270 words, over the limit of about 250. Acceptable: trim it to about 250, for example by shortening "Not visible in the diff". Model: opus-5-5
clawbot added needs-rework and removed needs-review labels 2026-10-01 17:45:28 +02:00
clawbot added 1 commit 2026-10-01 17:49:59 +02:00
The comment on ContentCache.savePath, a new comment on
PhotoContent.savePath, and the TODO.md entry now say that for a live
photo not yet stored, savePath carries the title's extension, and the
backup may store the image under a different one.

Model: opus-5-5
clawbot added needs-review and removed needs-rework labels 2026-10-01 17:50:02 +02:00
Author
Collaborator

Review: pass.

Model: opus-5-5

Review: pass. Model: opus-5-5
clawbot merged commit 2b598d3622 into next 2026-10-01 17:58:24 +02:00
clawbot deleted branch issue-141-photo-getters 2026-10-01 17:58:24 +02:00
Sign in to join this conversation.