Save originals at photos/YYYY/YYYY-MM/YYYY-MM-DD/YYYY-MM-DD.fileID.ext; add photo.download() (closes #143)

Each original's save path is now
YYYY/YYYY-MM/YYYY-MM-DD/YYYY-MM-DD.<fileID><ext> under the library's
download directory, dated by takenAt in the machine's time zone. The
directory defaults to photos/ in the working directory, resolved once at
open. savePath is always a string, with or without a content cache, and
isLocal is true only when the original is there.

photo.download() puts the original at its save path: copied from the
cache when the cache holds it, fetched straight there otherwise.
lib.backup() uses the same code for each file, writes each file's JSON
beside it, and links collections/ to the save paths. The backup has no
originals/ folder.

Model: opus-5-5
This commit is contained in:
2026-10-01 20:56:39 +00:00
parent 67d554fb46
commit bab19660f5
15 changed files with 714 additions and 423 deletions
+11 -3
View File
@@ -36,6 +36,7 @@ import {
makeAlbumsAPI,
makePhotosAPI,
makeTimelineAPI,
type SavePathLookup,
type TimelineGroup,
} from "../../src/library/read.js";
import { Library } from "../../src/library/index.js";
@@ -101,12 +102,19 @@ const file = (
};
// Build the three API objects over one fixed projection, the way `Library`
// wires them over its live store.
// wires them over its live store. Save paths are covered in
// content-library.test.ts; these tests never ask for one.
const apis = (records: DerivedRecords) => {
const derive = () => records;
const saves: SavePathLookup = {
savePath: () => {
throw new Error("no save paths in these tests");
},
isLocal: () => false,
};
return {
albums: makeAlbumsAPI(derive),
photos: makePhotosAPI(derive),
albums: makeAlbumsAPI(derive, saves),
photos: makePhotosAPI(derive, saves),
timeline: makeTimelineAPI(derive),
};
};