quak collections reads the collection list alone (closes #210)
check / check (push) Waiting to run

On an empty cache the read commands opened their library with a refresh that fetched and decrypted the files of every collection in turn, printing nothing and storing nothing until it had them all, so `quak collections` seemed to hang on a large account. The new `lib.freshCollections()` is the fresh read of the collection list alone: one request, and nothing stored, so a later refresh still fetches every collection's files. The read commands open their library with the new `refreshOnOpen: false` and make only the refresh they ask for. A refresh sends a "progress" event after each collection whose files it fetched; the commands that wait on one print it on stderr, except with `--json`.

Model: opus-5-5
This commit was merged in pull request #214.
This commit is contained in:
2026-10-18 19:01:34 +02:00
parent 7ec71bf7ec
commit 982b878d69
9 changed files with 537 additions and 58 deletions
+25 -10
View File
@@ -125,15 +125,29 @@ const readLibraryClient = (client: Client): LibraryClient => ({
// Open a library for a single point command: the aggressive background precache
// (issue #48) is off — a one-shot `collections` or `get` must not start
// downloading the whole account — and the refresh interval is long so no second
// refresh fires mid-command.
const openReadLibrary = (ctx: CliContext, client: Client): Promise<Library> =>
// downloading the whole account — and no refresh starts as it opens or on a
// timer: each command asks for the one it needs, and `collections` needs none
// (issue #210). While a refresh runs, each collection whose files it has
// fetched is counted on stderr, unless `json` is set, so a large account shows
// progress rather than nothing.
const openReadLibrary = (
ctx: CliContext,
client: Client,
opts: { json?: boolean } = {},
): Promise<Library> =>
Library.open({
client: readLibraryClient(client),
cacheDirectory: ctx.cacheDir,
refreshIntervalSeconds: 3600,
refreshOnOpen: false,
refreshIntervalSeconds: Infinity,
precacheThumbnails: false,
precacheOriginals: false,
onProgress: (event) => {
if (opts.json || event.status !== "progress") return;
ctx.stderr.write(
`Fetched the files of ${event.fetched} of ${event.total} collections\n`,
);
},
});
export const loginCommand = async (ctx: CliContext): Promise<number> => {
@@ -221,9 +235,10 @@ export const collectionsCommand = async (
if (!client) return 3;
const lib = await openReadLibrary(ctx, client);
try {
// Force a server round-trip and list in enumeration order (issue #36
// amendment, issue #52): the pre-library CLI printed current state in
// this order, not the albums projection's newest-first order.
// Ask the server for the collection list alone and list it in its
// order (issue #36 amendment, issues #52 and #210): the pre-library
// CLI printed current state in this order, not the albums
// projection's newest-first order.
const collections = await freshCollections(lib);
if (opts.json) {
@@ -264,7 +279,7 @@ export const filesCommand = async (
const collectionID = parseID(ctx, "collection", opts.collection);
if (collectionID === undefined) return 1;
const lib = await openReadLibrary(ctx, client);
const lib = await openReadLibrary(ctx, client, opts);
try {
// Force a server round-trip and list in enumeration order (issue #36
// amendment, issue #52). Each file prints from its own decrypted
@@ -490,7 +505,7 @@ export const listMissingThumbnailsCommand = async (
await init();
const client = requireSession(ctx);
if (!client) return 3;
const lib = await openReadLibrary(ctx, client);
const lib = await openReadLibrary(ctx, client, opts);
try {
// Refresh first so files added since the cache was written are
// checked; a failed refresh throws.
@@ -535,7 +550,7 @@ export const fixMissingThumbnailsCommand = async (
if (fileID === undefined) return 1;
fileIDs.push(fileID);
}
const lib = await openReadLibrary(ctx, client);
const lib = await openReadLibrary(ctx, client, opts);
try {
// Refresh first so files added since the cache was written are found;
// a failed refresh throws.