Skip to content

Releases: tsvb/PhotoDropMac

PhotoDropMac 0.1.3

Choose a tag to compare

@tsvb tsvb released this 06 Aug 17:06

Fixes a manifest-durability bug: concurrent ingests into the same library destroyed each other's manifest.

Upgrading is a straight replace — drag the new app over the old one. Existing libraries, manifests, presets, and settings are untouched.

The fix

0.1.2 gave job filenames millisecond precision, which fixed back-to-back ingests. It did not fix concurrent ones. Measured against 0.1.2, ten photodrop ingest processes started at once into a single library:

10 concurrent ingests → 10 files copied, 2 manifests

Eight jobs' records were destroyed. Process launches cluster inside the same millisecond, so precision alone bought almost nothing under real concurrency.

Job filenames are now claimed with O_EXCL — the file is created atomically before anything is written, so a name can never be taken twice, and a -2, -3, … suffix is added only when a collision actually occurs. Ordinary runs keep clean, chronological filenames.

Same test against this build:

10 concurrent ingests → 10 files copied, 10 manifests
✓ All 10 files across 10 manifests match their recorded checksums.

This covers the manifest JSON, its CSV, and the job log.

Why it mattered. The files always copied correctly — what disappeared was the record of what was copied and at what checksum. That failure is silent: a manifest-based verify reports success over whatever records survive, so it prints ✓ All 2 files … and exits 0 for a ten-file library. If you ran concurrent ingests on an earlier version, photodrop verify <library> --xattr checks every file against its own embedded checksum, independent of any manifest.

Nothing to do for existing libraries. Manifests written by earlier versions keep verifying exactly as before.

Requirements

macOS 14.0 or later. Unsandboxed by design — it needs arbitrary card→destination filesystem access and calls diskutil to eject cards.

Verifying this download

SHA-256  82714c28dfdf00fc600cbcdbab4ccf8dcc4134a8c6e5686773170ebc93aaef7e
         PhotoDropMac-0.1.3.dmg
shasum -a 256 PhotoDropMac-0.1.3.dmg
xcrun stapler validate PhotoDropMac-0.1.3.dmg
xcrun stapler validate /Applications/PhotoDropMac.app
spctl -a -t exec -vvv /Applications/PhotoDropMac.app   # → source=Notarized Developer ID

Full changelog: v0.1.2...v0.1.3

PhotoDropMac 0.1.2 (deprecated — use 0.1.3)

Choose a tag to compare

@tsvb tsvb released this 06 Aug 13:38

Warning

Deprecated — superseded by 0.1.3. Please download that instead.

Concurrent ingests into the same library destroy each other's manifest in this build. Ten photodrop ingest processes started at once copied all ten files but left two manifests. (Back-to-back sequential ingests, which this release fixed, are fine.)

Signing and notarization are identical to 0.1.3 — no security or Gatekeeper difference, and nothing here is unsafe to run. It stays downloadable for reproducibility.

The files are fine — the risk is that verification silently covers fewer of them. A manifest-based verify reports success over whatever records survive, so it prints ✓ All 2 files … and exits 0 for a ten-file library. Check the count it reports against what you actually have. Every copied file also carries its own checksum as an extended attribute, independent of any manifest:

photodrop verify <library> --xattr

Libraries written by this version remain fully valid, and 0.1.3 verifies them unchanged.


Fixes a manifest-durability bug: two ingests finishing in the same second overwrote each other's manifest.

Upgrading is a straight replace — drag the new app over the old one. Existing libraries, manifests, presets, and settings are untouched.

The fix

A job's manifest and log are named ingest-<timestamp>, and that timestamp only resolved to seconds. Manifests are written atomically, meaning a same-second collision caused the second job to silently replace the first job's manifest.

That mattered because a manifest is the only record of what a job copied and at what checksum — losing one loses the ability to verify those files later. Two ingests landing in the same second is easy to hit with small cards.

Timestamps now include milliseconds (ingest-20260806-093823-074.json). Verified with the shipped build: three back-to-back ingests inside a single second produce three manifests, and all three jobs' records verify.

The timestamp format also moved into one place, so the manifest and log filenames cannot drift apart.

Nothing to do for existing libraries. Manifests written by 0.1.0 and 0.1.1 keep verifying exactly as before; only the filename of newly written ones changes.

Requirements

macOS 14.0 or later. Unsandboxed by design — it needs arbitrary card→destination filesystem access and calls diskutil to eject cards.

Verifying this download

SHA-256  f06b82ab3a8442cdf0d0f724637750a353d17685638b4014726b992439502db3
         PhotoDropMac-0.1.2.dmg
shasum -a 256 PhotoDropMac-0.1.2.dmg
xcrun stapler validate PhotoDropMac-0.1.2.dmg
xcrun stapler validate /Applications/PhotoDropMac.app
spctl -a -t exec -vvv /Applications/PhotoDropMac.app   # → source=Notarized Developer ID

Full changelog: v0.1.1...v0.1.2

PhotoDropMac 0.1.1 (deprecated — use 0.1.3)

Choose a tag to compare

@tsvb tsvb released this 06 Aug 12:58

Warning

Deprecated — superseded by 0.1.3. Please download that instead.

Two manifest-durability bugs are present in this build:

  • Concurrent ingests destroy each other's manifest, fixed in 0.1.3. Ten photodrop ingest processes started at once left two manifests out of ten.
  • Back-to-back ingests do too, fixed in 0.1.2. Job filenames resolved only to the second, so two ingests finishing in the same second collided.

Signing and notarization are identical across every release — no security or Gatekeeper difference, and nothing here is unsafe to run. It stays downloadable for reproducibility.

The files are fine — the risk is that verification silently covers fewer of them. A manifest-based verify reports success over whatever records survive, so it can print ✓ All 1 file … and exit 0 for a larger library. Check the count it reports against what you actually have. Every copied file also carries its own checksum as an extended attribute, independent of any manifest:

photodrop verify <library> --xattr

Libraries written by this version remain fully valid, and 0.1.3 verifies them unchanged.


Adds the app icon. 0.1.0 shipped with the generic blank icon — that is the only user-visible difference between the two releases.

No behaviour, API, or on-disk format change, so upgrading is a straight replace: drag the new app over the old one. Existing libraries, manifests, and presets are untouched.

The icon

Drawn to Apple's macOS icon grid — 824×824 body inside a 1024×1024 canvas, 185.4pt continuous corner — so it sits at the same optical size as every other app in your Dock. Artwork is a memory card over a downward arrow in the app's ultramarine.

The 16pt and 32pt sizes carry a deliberately simpler glyph (the arrow alone). Detail that reads at 512pt turns to mush at 16pt, and the full card-plus-arrow composition became ambiguous at that size.

It is generated by scripts/make-icon.swift rather than hand-drawn, so the geometry is reviewable and reproducible.

Also since 0.1.0

  • release.sh now staples the notarization ticket to the app, not just the DMG. Previously, once you dragged the app out of the disk image and discarded it, nothing on disk proved the app was notarized — Gatekeeper had to ask Apple at first launch, which fails when you are offline. Both the app and the DMG are stapled now, and the release script verifies both.

Requirements

macOS 14.0 or later. Unsandboxed by design — it needs arbitrary card→destination filesystem access and calls diskutil to eject cards.

Verifying this download

SHA-256  47a2d0a5ff16a55305f20444836c9afd9b54d914dfa9100710e3bd323485a455
         PhotoDropMac-0.1.1.dmg
shasum -a 256 PhotoDropMac-0.1.1.dmg
xcrun stapler validate PhotoDropMac-0.1.1.dmg
xcrun stapler validate /Applications/PhotoDropMac.app   # now passes too
spctl -a -t exec -vvv /Applications/PhotoDropMac.app    # → source=Notarized Developer ID

Full changelog: v0.1.0...v0.1.1

PhotoDropMac 0.1.0 (deprecated — use 0.1.3)

Choose a tag to compare

@tsvb tsvb released this 06 Aug 12:14

Warning

Deprecated — superseded by 0.1.3. Please download that instead.

Three things are missing from this build:

  • Manifest durability under concurrent ingests, fixed in 0.1.3. Ten photodrop ingest processes started at once left two manifests out of ten.
  • Manifest durability for back-to-back ingests, fixed in 0.1.2. Job filenames resolved only to the second, so two ingests finishing in the same second overwrote each other's manifest.
  • The app icon, added in 0.1.1. This build shows the generic blank icon.

Signing and notarization are identical across every release — no security or Gatekeeper difference, and nothing here is unsafe to run. It stays downloadable for reproducibility.

The files are fine — the risk is that verification silently covers fewer of them. A manifest-based verify reports success over whatever records survive, so it can print ✓ All 1 file … and exit 0 for a larger library. Check the count it reports against what you actually have. Every copied file also carries its own checksum as an extended attribute, independent of any manifest:

photodrop verify <library> --xattr

Libraries written by this version remain fully valid, and 0.1.3 verifies them unchanged.


First public release of PhotoDropMac — a native macOS app that ingests photos from removable memory cards into a date-organized library, with streaming hash verification, content-based deduplication, and any number of mirror destinations.

Install: download the DMG, drag to Applications. It's Developer ID-signed and notarized, and both the disk image and the app inside it carry a stapled notarization ticket — so it opens with no Gatekeeper warning even on a Mac that's offline at first launch.

What it does

  • Ingest — scan a card, preview the year/day folder tree or cull in a contact sheet, then copy each photo bundle (RAW + its sidecars, JPEG pair, and audio note move together, all-or-nothing) with a tee-hashed xxHash64 verify on every file.
  • Dedup — content-based across the whole destination root, so a renamed earlier import is still recognized.
  • Mirrors — primary plus any number of archive destinations, each independently verified (3-2-1 backups).
  • Receipts — every ingest writes a JSON + CSV manifest of what landed and its checksums, and stamps each file with its own checksum xattr.
  • Verify & heal — re-check a library against its manifests (or against the per-file xattrs, which survive a reorg). heal reports which damaged files can be restored from a mirror and where from; it never writes to your library, only emits a script you review and run.
  • photodrop CLIingest, verify, and heal headless, embedded in the app bundle and covered by the same notarization. Scheduled background verification via a launchd agent.

Security

This release ships the results of a red-team review of the manifest trust boundary. No earlier build was ever published, so nothing below ever reached users — but the issues are worth naming since they shape how the tool now treats data it didn't write.

A manifest is unauthenticated data sitting inside the library it describes, and verify/heal accept a target you didn't necessarily create — a shared library, a folder on a card, or a .json passed straight to the CLI. It's now treated as untrusted input:

  • Path containment (a20395b) — manifest-recorded paths are confined to the library root. Previously a ../../../.. entry made verify hash arbitrary files on disk and could make heal's restore script overwrite them. The restore script also no longer lets control characters in a filename forge what looks like a command, and it lists every directory it will copy from so the review it asks for is actually possible.
  • Manifest conflicts (da870c2) — when two manifests disagree about a file's checksum, that's reported rather than silently resolved in favour of whichever claims to be newest. Ordering came from a field inside the file itself, so planting one manifest could relabel a tampered file as verified.
  • Hardening (c2cfc22) — the hash cache now requires an exact nanosecond timestamp match before reusing a digest (a false match could silently skip a photo, invisibly to later verification); CSV filenames can't act as spreadsheet formulas; and the symlink defense in discovery is documented and tested.

Verification coverage grew from 111 to 123 tests, including regression tests for each of the above.

Behaviour note: if you delete a file from your library and re-ingest different content to the same path, verify now reports CONFLICT rather than passing silently. The library's own records genuinely disagree at that point, and that's what the report says.

Requirements

macOS 14.0 or later. Unsandboxed by design — it needs arbitrary card→destination filesystem access and calls diskutil to eject cards.

Verifying this download

SHA-256  c32e5596c5e6e23f444519859af91b049317855955bbaf225883ade619a652b9
         PhotoDropMac-0.1.0.dmg
shasum -a 256 PhotoDropMac-0.1.0.dmg
xcrun stapler validate PhotoDropMac-0.1.0.dmg
spctl -a -t exec -vvv /Applications/PhotoDropMac.app   # → source=Notarized Developer ID