Remove the content cache's handling of an earlier version's live-photo ZIP (closes #151) #152
@@ -361,8 +361,8 @@ const openPart = async (
|
|||||||
// written unpacked: each part is named `destination` with the extension
|
// written unpacked: each part is named `destination` with the extension
|
||||||
// replaced by its own entry's, and the two must differ ignoring case. When the
|
// replaced by its own entry's, and the two must differ ignoring case. When the
|
||||||
// file records a hash, `<imageHash>:<videoHash>` must match it, each over that
|
// file records a hash, `<imageHash>:<videoHash>` must match it, each over that
|
||||||
// part's own bytes. Only then is whatever was at `destination` removed and the
|
// part's own bytes. Only then are the image, then the video, renamed into
|
||||||
// image, then the video, renamed into place; on any failure neither is stored.
|
// place; on any failure neither is stored.
|
||||||
//
|
//
|
||||||
// The ZIP is chosen by its uploader and may expand enormously, so each part is
|
// The ZIP is chosen by its uploader and may expand enormously, so each part is
|
||||||
// written as it decompresses and never held, and the ZIP is refused once the
|
// written as it decompresses and never held, and the ZIP is refused once the
|
||||||
@@ -486,7 +486,6 @@ const decryptLivePhoto = async (
|
|||||||
await part.handle.sync();
|
await part.handle.sync();
|
||||||
await part.handle.close();
|
await part.handle.close();
|
||||||
}
|
}
|
||||||
await rm(destination, { force: true });
|
|
||||||
await rename(image.tmpPath, path);
|
await rename(image.tmpPath, path);
|
||||||
try {
|
try {
|
||||||
await rename(video.tmpPath, videoPath);
|
await rename(video.tmpPath, videoPath);
|
||||||
@@ -570,8 +569,7 @@ const fetchAndDecrypt = async (
|
|||||||
}, api.getRetryOptions());
|
}, api.getRetryOptions());
|
||||||
|
|
||||||
// Write `file`'s original to `outPath`. A live photo is written as its image
|
// Write `file`'s original to `outPath`. A live photo is written as its image
|
||||||
// and its video beside `outPath` instead, and whatever was at `outPath` is
|
// and its video beside `outPath` instead (see `decryptLivePhoto`).
|
||||||
// removed (see `decryptLivePhoto`).
|
|
||||||
export const downloadFile = async (
|
export const downloadFile = async (
|
||||||
api: ApiClient,
|
api: ApiClient,
|
||||||
file: EnteFile,
|
file: EnteFile,
|
||||||
|
|||||||
@@ -687,7 +687,10 @@ export class ContentCache implements PhotoContent, ThumbnailsAPI {
|
|||||||
await this.touch(cached.path);
|
await this.touch(cached.path);
|
||||||
return { ...cached, bytes: size, cached: true };
|
return { ...cached, bytes: size, cached: true };
|
||||||
}
|
}
|
||||||
// A recorded file that has since gone re-fetches below.
|
// A recorded file that has since gone re-fetches below. So does a
|
||||||
|
// live photo recorded with no video: the cache opened before the
|
||||||
|
// library's records said it is a live photo, while its image and
|
||||||
|
// video had no JSON file beside them yet.
|
||||||
known.delete(fileID);
|
known.delete(fileID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1883,15 +1883,6 @@ describe("downloadFile live photos", () => {
|
|||||||
expect(readdirSync(t.dir).sort()).toEqual(["f.JPG", "f.bin"]);
|
expect(readdirSync(t.dir).sort()).toEqual(["f.JPG", "f.bin"]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("replaces what was at the destination", async () => {
|
|
||||||
const t = setup(livePhotoZip(), livePhoto);
|
|
||||||
writeFileSync(t.outPath, livePhotoZip());
|
|
||||||
|
|
||||||
await t.run();
|
|
||||||
|
|
||||||
expect(readdirSync(t.dir).sort()).toEqual(["f.heic", "f.mov"]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("renames the image and then the video into place, each from its own temp file", async () => {
|
it("renames the image and then the video into place, each from its own temp file", async () => {
|
||||||
const t = setup(livePhotoZip(), livePhoto);
|
const t = setup(livePhotoZip(), livePhoto);
|
||||||
|
|
||||||
|
|||||||
@@ -596,6 +596,36 @@ describe("ContentCache live photos", () => {
|
|||||||
expect(second!.pathsFor(5)).toEqual({});
|
expect(second!.pathsFor(5)).toEqual({});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("fetches a live photo's image and video again when the cache opened before knowing it is a live photo and no JSON file names them", async () => {
|
||||||
|
const { file: live, body } = await asLivePhoto(file(5, "IMG_5.HEIC"));
|
||||||
|
mkdirSync(originals(), { recursive: true });
|
||||||
|
writeFileSync(join(originals(), "5.heic"), "an image");
|
||||||
|
writeFileSync(join(originals(), "5.mov"), "a video");
|
||||||
|
const cache = cacheOf([live], new Map([[5, body]]));
|
||||||
|
// Opened without being told that file 5 is a live photo, the cache
|
||||||
|
// records one of the two files as its original, with no video.
|
||||||
|
await cache.open();
|
||||||
|
|
||||||
|
const events: string[] = [];
|
||||||
|
const result = await cache.original(5, {
|
||||||
|
onProgress: (e) => events.push(e.status),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(events.at(-1)).toBe("done");
|
||||||
|
expect(result).toEqual({
|
||||||
|
path: join(originals(), "5.heic"),
|
||||||
|
videoPath: join(originals(), "5.mov"),
|
||||||
|
bytes: IMAGE.length,
|
||||||
|
});
|
||||||
|
expect(readFileSync(result.path)).toEqual(Buffer.from(IMAGE));
|
||||||
|
expect(readFileSync(result.videoPath!)).toEqual(Buffer.from(VIDEO));
|
||||||
|
expect(
|
||||||
|
JSON.parse(
|
||||||
|
readFileSync(join(originals(), "5.livephoto.json"), "utf-8"),
|
||||||
|
),
|
||||||
|
).toEqual({ image: "5.heic", video: "5.mov" });
|
||||||
|
});
|
||||||
|
|
||||||
it.each(["missing", "empty"])(
|
it.each(["missing", "empty"])(
|
||||||
"fetches a live photo again when the video its JSON file names is %s",
|
"fetches a live photo again when the video its JSON file names is %s",
|
||||||
async (state) => {
|
async (state) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user