1 Commits
Author SHA1 Message Date
sneak daa47068bc 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, and it keeps a newer copy
that a refresh stores while it fetches. It leaves the refresh cursor
alone, so the next refresh still fetches every other collection that
changed. `quak files` with no argument still waits for a refresh.

Model: opus-5-5
2026-10-26 00:02:01 +00:00
2 changed files with 55 additions and 4 deletions
+6 -3
View File
@@ -645,6 +645,10 @@ export class Library {
collectionKey: collection.key,
sinceTime: known ? known.updationTime : 0,
});
// A refresh that ran during the fetch may have stored a copy as new as
// `collection` or newer. Committing this result would replace it.
const stored = this.store.getCollection(collection.id);
if (stored && collection.updationTime <= stored.updationTime) return;
// The stored cursor stays where it is, so the next refresh still
// fetches the files of every other collection that changed.
await this.commit(
@@ -836,9 +840,8 @@ export class Library {
// Commit a collections page and the file pages fetched for its collections
// to the store, then save. A collection or file that does not decrypt is
// left out, as if deleted, with the reason kept in the store: the cursor
// moves past it, so no later refresh sees it again until the server's copy
// changes.
// left out, as if deleted, with the reason kept in the store, and no later
// refresh sees it again until the server's copy changes.
private async commit(
page: CollectionsPage,
filePages: { collectionID: number; page: FilesPage }[],
+49 -1
View File
@@ -19,7 +19,8 @@
* fetches the files of every collection (issue #210).
* 5. `refreshCollection()` brings one collection up to date in the store and
* leaves the stored cursor where it was, so the next refresh still fetches
* the files of every other collection that changed (issue #217).
* the files of every other collection that changed (issue #217). A newer
* copy that a refresh stores while it fetches the files is kept.
*
* The client is the same metadata-only mock the background-refresh tests use:
* no crypto, no network, scripted pages, and a record of each call. A long
@@ -424,4 +425,51 @@ describe("Library.refreshCollection", () => {
await lib.close();
}
});
// Collection 1 is stored at 100. `refreshCollection()` asks for it at 110,
// which adds file 1002. While that files request waits, a refresh stores
// it at 120, where 1002 is deleted again and 1003 added.
it("keeps a newer copy that a refresh stores while the files are fetched", async () => {
const { client, lib } = await openSeeded(cacheDirectory);
let release!: () => void;
const held = new Promise<void>((r) => (release = r));
try {
client.filesFor(
1,
{ files: [file(1002, 1, 110)], deleted: [], cursor: 110 },
{ files: [file(1003, 1, 120)], deleted: [1002], cursor: 120 },
);
client.collectionsQueue.push({
collections: [collection(1, 120)],
deleted: [],
cursor: 120,
});
// Hold the next files request, refreshCollection's, until the
// test releases it. The refresh's request after it is answered
// at once.
let fetchStarted!: () => void;
const started = new Promise<void>((r) => (fetchStarted = r));
const realFilesSince = client.filesSince.bind(client);
client.filesSince = async (args) => {
client.filesSince = realFilesSince;
const page = await realFilesSince(args);
fetchStarted();
await held;
return page;
};
const refreshing = lib.refreshCollection(collection(1, 110));
await started;
await lib.fresh();
release();
await refreshing;
expect(lib.getCollection(1)?.updationTime).toBe(120);
expect(lib.listFiles(1).map((f) => f.id)).toEqual([1001, 1003]);
} finally {
release();
await lib.close();
}
});
});