quak files COLLECTION fetches that collection's files alone (closes #217)
check / check (push) Canceled after 0s

`quak files COLLECTION` used to wait for a full refresh, which on an
empty cache fetched the files of every collection before it listed one.
It now reads the collection list, then calls the new
`Library.refreshCollection()`, which brings that one collection up to
date in the store as a refresh would, and saves it. It sends no request
for the files when the stored copy is current. It leaves the refresh
cursor alone, so the next refresh still fetches every other collection
that changed. `refreshOnce` and `refreshCollection` share the commit
step. `quak files` with no argument still waits for a refresh.

Model: opus-5-5
This commit is contained in:
2026-10-25 23:24:16 +00:00
parent 657266a60e
commit 8e06cd5723
8 changed files with 280 additions and 84 deletions
+34 -19
View File
@@ -3,13 +3,16 @@
* issue #36, issue #52).
*
* The `collections`, `files`, `get`, and `get-thumb` commands must answer for
* current server state, not the local cache. `files`, `get` and `get-thumb`
* force a `Library.fresh()` round-trip before they read; the stand-in library
* below serves nothing until `fresh()` has been awaited, so a helper that read
* without refreshing would come back empty and fail here. `collections` asks
* the library for the collection list alone (`freshCollections()`, issue
* #210), and must not wait for a refresh, which would fetch the files of every
* collection.
* current server state, not the local cache. `files` without a collection ID,
* `get` and `get-thumb` force a `Library.fresh()` round-trip before they read;
* the stand-in library below serves nothing until `fresh()` has been awaited,
* so a helper that read without refreshing would come back empty and fail
* here. `collections` asks the library for the collection list alone
* (`freshCollections()`, issue #210), and must not wait for a refresh, which
* would fetch the files of every collection. `files COLLECTION` reads that list
* too and then brings that one collection up to date
* (`refreshCollection()`, issue #217); the stand-in serves a collection's files
* once either has run.
*
* `collections` and `files` also list in the library's enumeration order
* (`listCollections`/`listFiles`) — the order the pre-library CLI printed — not
@@ -64,12 +67,14 @@ const file = (
// A library that reveals its records only after `fresh()` has been awaited, and
// serves them in the enumeration order it was given. `freshCollections()`
// answers the collection list in that order without a refresh. `photos.byID`
// returns a stand-in `Photo` carrying just the fileID the helper passes
// through.
// answers the collection list in that order without a refresh.
// `refreshCollection()` reveals the files of that one collection.
// `photos.byID` returns a stand-in `Photo` carrying just the fileID the helper
// passes through.
class FakeLibrary implements FreshReadLibrary {
freshCalls = 0;
freshCollectionsCalls = 0;
refreshCollectionIDs: number[] = [];
private refreshed = false;
constructor(
@@ -88,9 +93,8 @@ class FakeLibrary implements FreshReadLibrary {
return this.collections;
}
getCollection(id: number): Collection | undefined {
if (!this.refreshed) return undefined;
return this.collections.find((c) => c.id === id);
async refreshCollection(collection: Collection): Promise<void> {
this.refreshCollectionIDs.push(collection.id);
}
listCollections(): Collection[] {
@@ -98,7 +102,9 @@ class FakeLibrary implements FreshReadLibrary {
}
listFiles(collectionID: number): EnteFile[] {
return this.refreshed
const current =
this.refreshed || this.refreshCollectionIDs.includes(collectionID);
return current
? this.files.filter((f) => f.collectionID === collectionID)
: [];
}
@@ -138,18 +144,25 @@ describe("CLI read helpers (issue #36 amendment, issue #52)", () => {
expect(rows.map((c) => c.id)).not.toEqual(newestFirst);
});
it("freshFiles refreshes first, lists in enumeration order, keeps raw fields", async () => {
it("freshFiles brings the collection alone up to date, lists in enumeration order, keeps raw fields", async () => {
// Enumeration order by id 10, 11, 12; creationTimes ascending, so a
// newest-first sort would reverse them.
// newest-first sort would reverse them. Collection 2's file is not
// listed.
const files = [
file(10, 1, 1_700_000_000_000_000),
file(11, 1, 1_700_000_000_000_001),
file(12, 1, 1_700_000_000_000_002),
file(20, 2, 1_700_000_000_000_003),
];
const lib = new FakeLibrary([collection(1, 100)], files);
const lib = new FakeLibrary(
[collection(1, 100), collection(2, 100)],
files,
);
const rows = await freshFiles(lib, 1);
expect(lib.freshCalls).toBe(1);
expect(lib.freshCollectionsCalls).toBe(1);
expect(lib.refreshCollectionIDs).toEqual([1]);
expect(lib.freshCalls).toBe(0);
expect(rows?.map((f) => f.id)).toEqual([10, 11, 12]);
// Field values come from raw metadata: microsecond creationTime and the
@@ -206,7 +219,9 @@ describe("CLI read helpers (issue #36 amendment, issue #52)", () => {
it("freshFiles returns undefined for an unknown collection", async () => {
const lib = new FakeLibrary([collection(1, 100)], []);
const rows = await freshFiles(lib, 999);
expect(lib.freshCalls).toBe(1);
expect(lib.freshCollectionsCalls).toBe(1);
expect(lib.refreshCollectionIDs).toEqual([]);
expect(lib.freshCalls).toBe(0);
expect(rows).toBeUndefined();
});