diff --git a/src/library/content.ts b/src/library/content.ts index e8436d7..396fdb8 100644 --- a/src/library/content.ts +++ b/src/library/content.ts @@ -117,8 +117,9 @@ export interface ContentOptions { export interface PhotoContent { original(fileID: number, opts?: ContentOptions): Promise; thumbnail(fileID: number, opts?: ContentOptions): Promise; - // Put the original at its save path and return it there. - download(fileID: number): Promise; + // Put the original at the save path of `file`, the copy the `Photo` holds, + // and return it there. + download(file: EnteFile): Promise; } 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 { - 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 { 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 }; } diff --git a/src/library/read.ts b/src/library/read.ts index 694f2e8..ae040cb 100644 --- a/src/library/read.ts +++ b/src/library/read.ts @@ -134,7 +134,7 @@ export class Photo { // it, it is copied from there; otherwise it is fetched straight to // `savePath`. async download(): Promise { - return this.cacheOrThrow().download(this.rec.fileID); + return this.cacheOrThrow().download(this.file); } // As `original`, for the thumbnail, through the thumbnail pool. diff --git a/test/library/content-library.test.ts b/test/library/content-library.test.ts index 8fee61d..198afd9 100644 --- a/test/library/content-library.test.ts +++ b/test/library/content-library.test.ts @@ -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 { + this.refreshes++; + return { + collections: [ + { ...collection(1), updationTime: this.refreshes }, + ], + deleted: [], + cursor: this.refreshes, + }; + } + override async filesSince(): Promise { + 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 })!;