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 {
|
export class DiskSpacePrimitives implements SpacePrimitives {
|
||||||
rootPath: string;
|
rootPath: string;
|
||||||
|
fileListCache: FileMeta[] = [];
|
||||||
|
fileListCacheTime = 0;
|
||||||
|
fileListCacheUpdating: AbortController | null = null;
|
||||||
|
|
||||||
constructor(rootPath: string) {
|
constructor(rootPath: string) {
|
||||||
this.rootPath = Deno.realPathSync(rootPath);
|
this.rootPath = Deno.realPathSync(rootPath);
|
||||||
|
@ -92,6 +95,11 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
||||||
}
|
}
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
|
// Invalidate cache and trigger an update
|
||||||
|
this.fileListCache = [];
|
||||||
|
this.fileListCacheTime = 0;
|
||||||
|
this.updateCacheInBackground();
|
||||||
|
|
||||||
// Fetch new metadata
|
// Fetch new metadata
|
||||||
return this.getFileMeta(name);
|
return this.getFileMeta(name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -121,11 +129,44 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
||||||
async deleteFile(name: string): Promise<void> {
|
async deleteFile(name: string): Promise<void> {
|
||||||
const localPath = this.filenameToPath(name);
|
const localPath = this.filenameToPath(name);
|
||||||
await Deno.remove(localPath);
|
await Deno.remove(localPath);
|
||||||
|
|
||||||
|
// Invalidate cache and trigger an update
|
||||||
|
this.fileListCache = [];
|
||||||
|
this.fileListCacheTime = 0;
|
||||||
|
this.updateCacheInBackground();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchFileList(): Promise<FileMeta[]> {
|
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[] = [];
|
const allFiles: FileMeta[] = [];
|
||||||
for await (const file of walkPreserveSymlinks(this.rootPath)) {
|
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;
|
const fullPath = file.path;
|
||||||
try {
|
try {
|
||||||
const s = await Deno.stat(fullPath);
|
const s = await Deno.stat(fullPath);
|
||||||
|
@ -149,9 +190,42 @@ export class DiskSpacePrimitives implements SpacePrimitives {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return allFiles;
|
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(
|
async function* walkPreserveSymlinks(
|
||||||
|
|
Loading…
Reference in New Issue