check / check (push) Successful in 27s
Route every command through Library.open instead of scanning the client. collections/files read lib.albums; get/get-thumb resolve lib.photos.byID and copy the cached original/thumbnail to --out, so --collection is now accepted but ignored. backup-metadata and the thumbnail helpers enumerate through the library and read originals via photo.original(); ML fetch, EXIF, and thumbnail upload are unchanged. A new global --cache-dir sets the cache location; point commands open with the background precache off so a one-shot command never starts downloading the whole account. files, get, and get-thumb present each file from its own decrypted metadata (raw title, creationTime in microseconds) rather than the PhotoRecord projection, which prefers editedName/editedTime and milliseconds. This keeps the CLI output byte-identical to the pre-library version. Also addresses #17: fix-missing-thumbnails now reports a non-JPEG image or a video as skipped (unsupported), distinct from failed, and only a genuine failure exits non-zero. Model: opus-4-8
77 lines
2.9 KiB
TypeScript
77 lines
2.9 KiB
TypeScript
// The CLI presents a file by its own decrypted metadata, not the PhotoRecord
|
|
// projection (issue #52). For a renamed file the two disagree: the projection
|
|
// prefers `editedName` and reports `editedTime` in milliseconds, while the CLI
|
|
// must print the raw `metadata.title` and `metadata.creationTime` (microseconds)
|
|
// and name downloads after the raw title, byte-identical to the pre-library CLI.
|
|
//
|
|
// This locks in that contrast: the shared output helpers emit the raw values,
|
|
// and the projection of the same file emits the edited ones — so a regression
|
|
// that re-sourced the CLI from the projection would fail here.
|
|
|
|
import { describe, it, expect } from "vitest";
|
|
|
|
import {
|
|
fileListRow,
|
|
fileListLine,
|
|
originalName,
|
|
thumbnailName,
|
|
} from "../../src/cli-output.js";
|
|
import { deriveRecords } from "../../src/library/records.js";
|
|
import type { EnteFile } from "../../src/model/types.js";
|
|
|
|
// Microseconds, as Ente stores times.
|
|
const RAW_CREATION = 1700000000000000;
|
|
const EDITED_TIME = 1710000000000000;
|
|
const RAW_TITLE = "IMG_0001.HEIC";
|
|
const EDITED_NAME = "Sunset.heic";
|
|
|
|
// A file the user has renamed and re-dated: basic metadata holds the original
|
|
// title and capture time; public magic metadata holds the edits.
|
|
const renamedFile: EnteFile = {
|
|
id: 100,
|
|
collectionID: 10,
|
|
ownerID: 42,
|
|
key: new Uint8Array(),
|
|
metadata: {
|
|
title: RAW_TITLE,
|
|
fileType: "image",
|
|
creationTime: RAW_CREATION,
|
|
modificationTime: RAW_CREATION,
|
|
},
|
|
pubMagicMetadata: { editedName: EDITED_NAME, editedTime: EDITED_TIME },
|
|
file: { decryptionHeader: "" },
|
|
thumbnail: { decryptionHeader: "" },
|
|
updationTime: RAW_CREATION,
|
|
};
|
|
|
|
describe("CLI file output (issue #52)", () => {
|
|
it("emits the raw title and microsecond creationTime for --json", () => {
|
|
expect(fileListRow(renamedFile)).toEqual({
|
|
id: 100,
|
|
title: RAW_TITLE,
|
|
fileType: "image",
|
|
creationTime: RAW_CREATION,
|
|
collectionID: 10,
|
|
});
|
|
});
|
|
|
|
it("emits the raw title in the human column", () => {
|
|
expect(fileListLine(renamedFile)).toBe(`100\timage\t${RAW_TITLE}`);
|
|
});
|
|
|
|
it("names downloads after the raw title", () => {
|
|
expect(originalName(renamedFile)).toBe(RAW_TITLE);
|
|
expect(thumbnailName(renamedFile)).toBe(`thumb_${RAW_TITLE}`);
|
|
});
|
|
|
|
it("does not use the editedName/editedTime projection", () => {
|
|
const record = deriveRecords([], [renamedFile]).photos.get(100);
|
|
// The projection prefers the edits and reports milliseconds; the CLI
|
|
// helpers above deliberately do not.
|
|
expect(record?.title).toBe(EDITED_NAME);
|
|
expect(record?.takenAt).toBe(Math.floor(EDITED_TIME / 1000));
|
|
expect(fileListRow(renamedFile).title).not.toBe(record?.title);
|
|
expect(fileListRow(renamedFile).creationTime).not.toBe(record?.takenAt);
|
|
});
|
|
});
|