Save path ./photos/YYYY/YYYY-MM/YYYY-MM-DD/YYYY-MM-DD.fileID.ext; download() from the cache first (closes #143)
check / check (push) Successful in 1m25s
check / check (push) Successful in 1m25s
Originals are saved at `{downloadDirectory}/YYYY/YYYY-MM/YYYY-MM-DD/YYYY-MM-DD.{fileID}{ext}`. The date is the photo's `takenAt` in local time, and `downloadDirectory` defaults to `./photos`, resolved when the library opens. The old `originals/` layout is gone.
`photo.download()` writes the original to `savePath`. It copies from the cache when the cache holds the original, and fetches otherwise. `lib.backup()` uses the same path and rule, and every album in `collections/` links to it. `isLocal` is true only when the original is at `savePath`.
For a file in several albums, one rule picks the copy everything uses: the most recently synced, with the lowest album ID breaking a tie.
Model: opus-5-5
This commit was merged in pull request #146.
This commit is contained in:
+33
-15
@@ -86,6 +86,10 @@ export interface LibraryChange {
|
||||
export interface DerivedRecords {
|
||||
albums: Map<number, AlbumRecord>;
|
||||
photos: Map<number, PhotoRecord>;
|
||||
// The membership each photo's record is read from, for its `Photo`'s save
|
||||
// path. It holds the file's key, so it stays in this process: no snapshot
|
||||
// or change carries it.
|
||||
files: Map<number, EnteFile>;
|
||||
}
|
||||
|
||||
const asString = (v: unknown): string | undefined =>
|
||||
@@ -101,18 +105,19 @@ const microsToMillis = (micros: number): number => Math.floor(micros / 1000);
|
||||
const byNewestPhoto = (a: PhotoRecord, b: PhotoRecord): number =>
|
||||
b.takenAt - a.takenAt || b.fileID - a.fileID;
|
||||
|
||||
// Build one PhotoRecord from every membership of a file. The memberships share
|
||||
// the same underlying file, so metadata is read from a single representative
|
||||
// (the most recently synced, lowest collection id to break ties); `albumIDs`
|
||||
// gathers them all.
|
||||
const toPhotoRecord = (
|
||||
fileID: number,
|
||||
memberships: EnteFile[],
|
||||
): PhotoRecord => {
|
||||
const albumIDs = memberships
|
||||
.map((m) => m.collectionID)
|
||||
.sort((a, b) => a - b);
|
||||
const rep = memberships.reduce((best, m) =>
|
||||
// A photo's `takenAt` in milliseconds: `pubMagicMetadata.editedTime` when the
|
||||
// user edited the date, else basic-metadata `creationTime`.
|
||||
export const takenAtOf = (file: EnteFile): number =>
|
||||
microsToMillis(
|
||||
asNumber(file.pubMagicMetadata?.editedTime) ??
|
||||
file.metadata.creationTime,
|
||||
);
|
||||
|
||||
// The membership a file's record is read from: the most recently synced, lowest
|
||||
// collection id to break ties. Whatever dates a file's save path takes this
|
||||
// membership too, so the path always carries the record's `takenAt`.
|
||||
export const representative = (memberships: EnteFile[]): EnteFile =>
|
||||
memberships.reduce((best, m) =>
|
||||
m.updationTime > best.updationTime ||
|
||||
(m.updationTime === best.updationTime &&
|
||||
m.collectionID < best.collectionID)
|
||||
@@ -120,17 +125,28 @@ const toPhotoRecord = (
|
||||
: best,
|
||||
);
|
||||
|
||||
// Build one PhotoRecord from every membership of a file. The memberships share
|
||||
// the same underlying file, so metadata is read from a single representative;
|
||||
// `albumIDs` gathers them all.
|
||||
const toPhotoRecord = (
|
||||
fileID: number,
|
||||
memberships: EnteFile[],
|
||||
): PhotoRecord => {
|
||||
const albumIDs = memberships
|
||||
.map((m) => m.collectionID)
|
||||
.sort((a, b) => a - b);
|
||||
const rep = representative(memberships);
|
||||
|
||||
const pub = rep.pubMagicMetadata ?? {};
|
||||
const priv = rep.magicMetadata ?? {};
|
||||
|
||||
const takenAtMicros = asNumber(pub.editedTime) ?? rep.metadata.creationTime;
|
||||
const visibility = asNumber(priv.visibility);
|
||||
|
||||
const record: PhotoRecord = {
|
||||
fileID,
|
||||
albumIDs,
|
||||
title: asString(pub.editedName) ?? rep.metadata.title,
|
||||
takenAt: microsToMillis(takenAtMicros),
|
||||
takenAt: takenAtOf(rep),
|
||||
modifiedAt: microsToMillis(rep.metadata.modificationTime),
|
||||
fileType: rep.metadata.fileType,
|
||||
isArchived: visibility === VISIBILITY_ARCHIVED,
|
||||
@@ -198,6 +214,7 @@ export const deriveRecords = (
|
||||
}
|
||||
|
||||
const photos = new Map<number, PhotoRecord>();
|
||||
const photoFiles = new Map<number, EnteFile>();
|
||||
const takenAtByFile = new Map<number, number>();
|
||||
for (const [fileID, memberships] of byFileID) {
|
||||
const record = toPhotoRecord(fileID, memberships);
|
||||
@@ -209,6 +226,7 @@ export const deriveRecords = (
|
||||
record.thumbnailPath = paths.thumbnailPath;
|
||||
}
|
||||
photos.set(fileID, record);
|
||||
photoFiles.set(fileID, representative(memberships));
|
||||
takenAtByFile.set(fileID, record.takenAt);
|
||||
}
|
||||
|
||||
@@ -217,7 +235,7 @@ export const deriveRecords = (
|
||||
albums.set(c.id, toAlbumRecord(c, files, takenAtByFile));
|
||||
}
|
||||
|
||||
return { albums, photos };
|
||||
return { albums, photos, files: photoFiles };
|
||||
};
|
||||
|
||||
// Sorted, GUI-ready arrays: albums newest updated first, photos newest first.
|
||||
|
||||
Reference in New Issue
Block a user