Add an in-memory cache of the file list for disk spaces (#1012)
Add an in-memory cache of the file list for disk spacespull/1027/head
parent
4af100b7b7
commit
280fe2bec8
|
@ -16,6 +16,9 @@ const excludedFiles = ["data.db", "data.db-journal", "sync.json"];
|
|||
|
||||
export class DiskSpacePrimitives implements SpacePrimitives {
|
||||
rootPath: string;
|
||||
fileListCache: FileMeta[] = [];
|
||||
fileListCacheTime = 0;
|
||||
fileListCacheUpdating: AbortController | null = null;
|
||||
|
||||
constructor(rootPath: string) {
|
||||
this.rootPath = Deno.realPathSync(rootPath);
|
||||
|
@ -92,6 +95,11 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||
}
|
||||
file.close();
|
||||
|
||||
// Invalidate cache and trigger an update
|
||||
this.fileListCache = [];
|
||||
this.fileListCacheTime = 0;
|
||||
this.updateCacheInBackground();
|
||||
|
||||
// Fetch new metadata
|
||||
return this.getFileMeta(name);
|
||||
} catch (e) {
|
||||
|
@ -121,11 +129,44 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||
async deleteFile(name: string): Promise<void> {
|
||||
const localPath = this.filenameToPath(name);
|
||||
await Deno.remove(localPath);
|
||||
|
||||
// Invalidate cache and trigger an update
|
||||
this.fileListCache = [];
|
||||
this.fileListCacheTime = 0;
|
||||
this.updateCacheInBackground();
|
||||
}
|
||||
|
||||
async fetchFileList(): Promise<FileMeta[]> {
|
||||
// console.log("Fetching file list");
|
||||
const startTime = performance.now();
|
||||
|
||||
// If the file list cache is less than 60 seconds old, return it
|
||||
if (
|
||||
this.fileListCache.length > 0 &&
|
||||
startTime - this.fileListCacheTime < 60000
|
||||
) {
|
||||
// Trigger a background sync, but return the cached list while the cache is being updated
|
||||
this.updateCacheInBackground();
|
||||
return this.fileListCache;
|
||||
}
|
||||
|
||||
// Otherwise get the file list and wait for it
|
||||
const allFiles: FileMeta[] = await this.getFileList();
|
||||
|
||||
const endTime = performance.now();
|
||||
console.info("Fetched uncached file list in", endTime - startTime, "ms");
|
||||
|
||||
this.fileListCache = allFiles;
|
||||
this.fileListCacheTime = startTime;
|
||||
|
||||
return allFiles;
|
||||
}
|
||||
|
||||
private async getFileList(): Promise<FileMeta[]> {
|
||||
const allFiles: FileMeta[] = [];
|
||||
for await (const file of walkPreserveSymlinks(this.rootPath)) {
|
||||
// Uncomment to simulate a slow-ish disk
|
||||
// await new Promise((resolve) => setTimeout(resolve, 1));
|
||||
const fullPath = file.path;
|
||||
try {
|
||||
const s = await Deno.stat(fullPath);
|
||||
|
@ -149,9 +190,42 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allFiles;
|
||||
}
|
||||
|
||||
private updateCacheInBackground() {
|
||||
if (this.fileListCacheUpdating) {
|
||||
// Cancel the existing background update, so we never return stale data
|
||||
this.fileListCacheUpdating.abort();
|
||||
}
|
||||
|
||||
const abortController = new AbortController();
|
||||
this.fileListCacheUpdating = abortController;
|
||||
|
||||
const updatePromise = this.getFileList().then((allFiles) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
|
||||
this.fileListCache = allFiles;
|
||||
this.fileListCacheTime = performance.now();
|
||||
console.info(
|
||||
"Updated file list cache in background:",
|
||||
allFiles.length,
|
||||
"files found",
|
||||
);
|
||||
}).catch((error) => {
|
||||
if (abortController.signal.aborted) return;
|
||||
|
||||
if (error.name !== "AbortError") {
|
||||
console.error("Error updating file list cache in background:", error);
|
||||
}
|
||||
}).finally(() => {
|
||||
if (this.fileListCacheUpdating === abortController) {
|
||||
this.fileListCacheUpdating = null;
|
||||
}
|
||||
});
|
||||
|
||||
return updatePromise;
|
||||
}
|
||||
}
|
||||
|
||||
async function* walkPreserveSymlinks(
|
||||
|
|
Loading…
Reference in New Issue