Have photo.download() write to the save path of the Photo's own file copy
check / check (push) Successful in 4m9s

Photo.download() now hands the content cache the file copy the Photo
holds, the one savePath and isLocal are dated from, instead of the
fileID the cache then looked up in the store. A Photo held across a
refresh that brings a date edited in Ente is now downloaded to the path
its savePath names. A test covers that case.

Model: opus-5-5
This commit is contained in:
2026-10-01 21:01:33 +00:00
parent 8992c7c5a9
commit fc06b9100b
3 changed files with 54 additions and 11 deletions
+11 -10
View File
@@ -117,8 +117,9 @@ export interface ContentOptions {
export interface PhotoContent {
original(fileID: number, opts?: ContentOptions): Promise<ContentResult>;
thumbnail(fileID: number, opts?: ContentOptions): Promise<ContentResult>;
// Put the original at its save path and return it there.
download(fileID: number): Promise<ContentResult>;
// Put the original at the save path of `file`, the copy the `Photo` holds,
// and return it there.
download(file: EnteFile): Promise<ContentResult>;
}
export interface EnsureResult {
@@ -531,18 +532,18 @@ export class ContentCache implements PhotoContent, ThumbnailsAPI {
return this.get(fileID, "thumbnail", "on-demand", opts?.onProgress);
}
// Put the original at its save path under the download directory and
// return it there. One already stored there is returned as it is; one the
// cache holds is copied from it; any other is fetched straight to the
// save path, with no copy left in the cache.
async download(fileID: number): Promise<ContentResult> {
const file = this.getFile(fileID);
if (!file) throw new Error(`content cache: unknown file ${fileID}`);
// Put the original at the save path of `file` under the download directory
// and return it there. `file` is the copy the `Photo` holds, so the path is
// the one its `savePath` names, even after a refresh changed the date. One
// already stored there is returned as it is; one the cache holds is copied
// from it; any other is fetched straight to the save path, with no copy
// left in the cache.
async download(file: EnteFile): Promise<ContentResult> {
const root = this.downloadDirectory;
const saved =
storedAtSavePath(root, file) ??
(await placeOriginal(root, file, (dest) =>
this.backupOriginal(fileID, dest),
this.backupOriginal(file.id, dest),
));
return { ...saved, bytes: fileSize(saved.path) ?? 0 };
}
+1 -1
View File
@@ -134,7 +134,7 @@ export class Photo {
// it, it is copied from there; otherwise it is fetched straight to
// `savePath`.
async download(): Promise<ContentResult> {
return this.cacheOrThrow().download(this.rec.fileID);
return this.cacheOrThrow().download(this.file);
}
// As `original`, for the thumbnail, through the thumbnail pool.
+42
View File
@@ -466,6 +466,48 @@ describe("Photo save path, local copy, content and EXIF", () => {
await lib.close();
});
it("downloads a photo held across a refresh that edits its date to the save path it names", async () => {
// The same account, whose second refresh brings a date edited in Ente.
const edited = file(1, 1);
edited.updationTime = 2;
edited.pubMagicMetadata = {
editedTime: new Date(2026, 3, 15, 12).getTime() * 1000,
};
class DateEditedClient extends MockClient {
refreshes = 0;
override async collectionsSince(): Promise<CollectionsPage> {
this.refreshes++;
return {
collections: [
{ ...collection(1), updationTime: this.refreshes },
],
deleted: [],
cursor: this.refreshes,
};
}
override async filesSince(): Promise<FilesPage> {
return {
files: [this.refreshes === 1 ? file(1, 1) : edited],
deleted: [],
cursor: this.refreshes,
};
}
}
const lib = await open({ client: new DateEditedClient() });
const photo = lib.photos.byID({ fileID: 1 })!;
await lib.fresh();
expect(lib.photos.byID({ fileID: 1 })!.takenAt).toBe(
new Date(2026, 3, 15, 12).getTime(),
);
const saved = await photo.download();
expect(saved.path).toBe(photo.savePath);
expect(saved.path).toBe(join(root, "backup", DAY, "2026-03-01.1.jpg"));
expect(photo.isLocal).toBe(true);
await lib.close();
});
it("has a save path, and is not local, without a content source", async () => {
const lib = await open({ contentSource: undefined });
const photo = lib.photos.byID({ fileID: 1 })!;