Camera uploads stop overwriting each other

iOS names every camera capture "image.jpg"; the replace-to-reshoot
semantics (same name = re-extract this photo) then silently destroyed
the previous shot — which is how a shelf photo vanished today. Generic
capture names (image/photo/img/capture stems) now get minted unique
names (shelf-<timestamp>[-n]) server-side, colliding names within one
batch uniquify too, and an identical re-send of the same shot dedupes
to a no-op. Named photos (IMG_1234.jpeg) keep the deliberate reshoot
replacement flow. The upload feedback shows the minted names, so the
phone sees exactly what landed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-03 17:11:24 -04:00
co-authored by Claude Fable 5
parent cf6dd5e134
commit 9610f3b774
2 changed files with 63 additions and 5 deletions
+32
View File
@@ -1059,3 +1059,35 @@ def test_home_screen_icon_is_served_and_public(tmp_path):
assert phone.get("/static/apple-touch-icon.png").status_code == 200
page = phone.get("/?k=sekret", follow_redirects=True)
assert 'rel="apple-touch-icon"' in page.text
def test_generic_camera_names_never_overwrite(tmp_path):
web, cfg = make_client(tmp_path)
def send(*photos):
return web.post(
"/api/photos",
files=[("files", (name, data, "image/jpeg")) for name, data in photos],
)
# two camera captures in one batch, both "image.jpg" (the iOS shape)
res = send(("image.jpg", b"\xff\xd8first"), ("image.jpg", b"\xff\xd8second"))
assert res.status_code == 200
saved = res.json()["saved"]
assert len(saved) == len(set(saved)) == 2
assert all(n.startswith("shelf-") for n in saved)
on_disk = {p.name: p.read_bytes() for p in cfg.photos_dir.glob("shelf-*")}
assert set(on_disk.values()) == {b"\xff\xd8first", b"\xff\xd8second"}
# a third capture later gets its own name too
res2 = send(("image.jpg", b"\xff\xd8third"))
(third,) = res2.json()["saved"]
assert third.startswith("shelf-") and third not in saved
# a named photo keeps the deliberate replace-to-reshoot semantics
(cfg.extract_raw_dir).mkdir(parents=True, exist_ok=True)
(cfg.extract_raw_dir / "IMG_9999.jpeg.json").write_text("{}")
res3 = send(("IMG_9999.jpeg", b"\xff\xd8reshoot"))
assert res3.json()["saved"] == ["IMG_9999.jpeg"]
assert (cfg.photos_dir / "IMG_9999.jpeg").read_bytes() == b"\xff\xd8reshoot"
assert not (cfg.extract_raw_dir / "IMG_9999.jpeg.json").exists()