Store live photos as their image and their video (closes #107)
check / check (push) Successful in 41s
check / check (push) Successful in 41s
A live photo, which Ente stores as one ZIP, is unpacked as it downloads into its image and its video, each `<fileID>.<ext>` with its extension from the ZIP, beside `<fileID>.livephoto.json`, which names the two. Both are checked against the recorded hash and renamed into place only when both are complete. A ZIP with a second image or video, or whose parts come to more than 20 times its size plus 16 MiB, is refused. The backup and the content cache count a live photo as stored only with both files, album folders link both, `quak get` writes both, and the content result gives the video as `videoPath`. A ZIP an earlier version stored is replaced. Model: opus-5-5
This commit was merged in pull request #128.
This commit is contained in:
@@ -10,14 +10,21 @@
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { mkdtempSync, rmSync, existsSync, writeFileSync } from "node:fs";
|
||||
import {
|
||||
mkdtempSync,
|
||||
readdirSync,
|
||||
rmSync,
|
||||
existsSync,
|
||||
writeFileSync,
|
||||
} from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { Library } from "../../src/library/index.js";
|
||||
import { Library, type LibraryOptions } from "../../src/library/index.js";
|
||||
import type { ContentSource } from "../../src/library/content.js";
|
||||
import type { CollectionsPage, FilesPage } from "../../src/client.js";
|
||||
import type { Collection, EnteFile } from "../../src/model/types.js";
|
||||
import { asLivePhoto, cdnSource, livePhotoZip } from "../live-photo.js";
|
||||
|
||||
const USER_ID = 7;
|
||||
|
||||
@@ -142,6 +149,52 @@ describe("Library content wiring", () => {
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("removes a live photo's ZIP an earlier version cached when it opens, and precaches its image and video", async () => {
|
||||
const { file: live, body } = await asLivePhoto(file(1, 1));
|
||||
class LiveClient extends MockClient {
|
||||
override async filesSince(): Promise<FilesPage> {
|
||||
return { files: [live], deleted: [], cursor: 1 };
|
||||
}
|
||||
}
|
||||
const originals = join(root, "cache", "originals");
|
||||
const open = (opts: Partial<LibraryOptions>): Promise<Library> =>
|
||||
Library.open({
|
||||
client: new LiveClient(),
|
||||
cacheDirectory: join(root, "cache"),
|
||||
contentSource: cdnSource(new Map([[1, body]])),
|
||||
refreshIntervalSeconds: 3600,
|
||||
precacheThumbnails: false,
|
||||
precacheOriginals: false,
|
||||
...opts,
|
||||
});
|
||||
// A first run records the library, so the next one knows that file 1
|
||||
// is a live photo when it opens the cache.
|
||||
await (await open({})).close();
|
||||
writeFileSync(join(originals, "1.jpg"), livePhotoZip());
|
||||
|
||||
let precached!: () => void;
|
||||
const done = new Promise<void>((r) => (precached = r));
|
||||
const lib = await open({
|
||||
precacheOriginals: true,
|
||||
onProgress: (e) => {
|
||||
if (e.operation === "precacheOriginals" && e.status === "done")
|
||||
precached();
|
||||
},
|
||||
});
|
||||
expect(existsSync(join(originals, "1.jpg"))).toBe(false);
|
||||
await done;
|
||||
|
||||
expect(readdirSync(originals).sort()).toEqual([
|
||||
"1.heic",
|
||||
"1.livephoto.json",
|
||||
"1.mov",
|
||||
]);
|
||||
expect(lib.photos.byID({ fileID: 1 })!.record().originalPath).toBe(
|
||||
join(originals, "1.heic"),
|
||||
);
|
||||
await lib.close();
|
||||
});
|
||||
|
||||
it("throws from content methods when opened without a content source", async () => {
|
||||
const lib = await Library.open({
|
||||
client: new MockClient(),
|
||||
|
||||
Reference in New Issue
Block a user