quak files COLLECTION fetches that collection's files alone (closes #217)
check / check (push) Canceled after 0s
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
This commit is contained in:
@@ -17,6 +17,10 @@
|
||||
* 4. `freshCollections()` reads the collection list alone, with one request
|
||||
* and no collection's files, and stores nothing, so the next refresh still
|
||||
* 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). 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
|
||||
@@ -340,3 +344,132 @@ describe("Library.freshCollections", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Library.refreshCollection", () => {
|
||||
let dir: string;
|
||||
let cacheDirectory: string;
|
||||
|
||||
beforeEach(() => {
|
||||
dir = mkdtempSync(join(tmpdir(), "quak-refresh-collection-"));
|
||||
cacheDirectory = join(dir, "cache");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
// Collections 1 and 2 are stored at updationTime 100, with a stored cursor
|
||||
// of 100, and then both change on the server.
|
||||
it("fetches the files of one changed collection and leaves the others to the next refresh", async () => {
|
||||
const client = new MockClient();
|
||||
client.collectionsQueue.push({
|
||||
collections: [collection(1, 100), collection(2, 100)],
|
||||
deleted: [],
|
||||
cursor: 100,
|
||||
});
|
||||
client.filesFor(1, {
|
||||
files: [file(1001, 1, 90)],
|
||||
deleted: [],
|
||||
cursor: 90,
|
||||
});
|
||||
client.filesFor(2, {
|
||||
files: [file(2001, 2, 90)],
|
||||
deleted: [],
|
||||
cursor: 90,
|
||||
});
|
||||
const lib = await Library.open({
|
||||
client,
|
||||
cacheDirectory,
|
||||
refreshIntervalSeconds: SLOW_INTERVAL,
|
||||
});
|
||||
try {
|
||||
client.filesCalls.length = 0;
|
||||
client.filesFor(1, {
|
||||
files: [file(1002, 1, 110)],
|
||||
deleted: [],
|
||||
cursor: 110,
|
||||
});
|
||||
client.filesFor(2, {
|
||||
files: [file(2002, 2, 120)],
|
||||
deleted: [],
|
||||
cursor: 120,
|
||||
});
|
||||
|
||||
// Collection 1's files alone, since the stored copy.
|
||||
await lib.refreshCollection(collection(1, 110));
|
||||
expect(client.filesCalls).toEqual([
|
||||
{ collectionID: 1, sinceTime: 100 },
|
||||
]);
|
||||
expect(lib.listFiles(1).map((f) => f.id)).toEqual([1001, 1002]);
|
||||
expect(lib.listFiles(2).map((f) => f.id)).toEqual([2001]);
|
||||
|
||||
// The stored copy is now current, so no request.
|
||||
await lib.refreshCollection(collection(1, 110));
|
||||
expect(client.filesCalls).toHaveLength(1);
|
||||
|
||||
// The next refresh asks from the stored cursor, 100, so it still
|
||||
// sees collection 2 changed and fetches its files.
|
||||
client.collectionsQueue.push({
|
||||
collections: [collection(1, 110), collection(2, 120)],
|
||||
deleted: [],
|
||||
cursor: 120,
|
||||
});
|
||||
await lib.fresh();
|
||||
expect(client.collectionsSinceTimes).toEqual([0, 100]);
|
||||
expect(client.filesCalls).toEqual([
|
||||
{ collectionID: 1, sinceTime: 100 },
|
||||
{ collectionID: 2, sinceTime: 100 },
|
||||
]);
|
||||
expect(lib.listFiles(2).map((f) => f.id)).toEqual([2001, 2002]);
|
||||
} finally {
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user