Hopefully finally squashed the race condition that would randomly reload files
This commit is contained in:
parent
d388796b1c
commit
79611a27e0
@ -12,7 +12,9 @@ import type { SpacePrimitives } from "./space_primitives.ts";
|
||||
* - page:deleted (string)
|
||||
*/
|
||||
export class EventedSpacePrimitives implements SpacePrimitives {
|
||||
// Avoid file listing while performing another fetch (read, write, meta) operation
|
||||
alreadyFetching = false;
|
||||
|
||||
initialFileListLoad = true;
|
||||
|
||||
spaceSnapshot: Record<string, number> = {};
|
||||
@ -28,55 +30,73 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
||||
async fetchFileList(): Promise<FileMeta[]> {
|
||||
const newFileList = await this.wrapped.fetchFileList();
|
||||
if (this.alreadyFetching) {
|
||||
// Avoid race conditions
|
||||
// Avoid race conditions, let's just skip this in terms of event triggering: that's ok
|
||||
return newFileList;
|
||||
}
|
||||
this.alreadyFetching = true;
|
||||
const deletedFiles = new Set<string>(Object.keys(this.spaceSnapshot));
|
||||
for (const meta of newFileList) {
|
||||
const oldHash = this.spaceSnapshot[meta.name];
|
||||
const newHash = meta.lastModified;
|
||||
if (
|
||||
(
|
||||
// New file scenario
|
||||
!oldHash && !this.initialFileListLoad
|
||||
) || (
|
||||
// Changed file scenario
|
||||
oldHash &&
|
||||
oldHash !== newHash
|
||||
)
|
||||
) {
|
||||
await this.dispatchEvent("file:changed", meta.name);
|
||||
try {
|
||||
this.alreadyFetching = true;
|
||||
const deletedFiles = new Set<string>(Object.keys(this.spaceSnapshot));
|
||||
for (const meta of newFileList) {
|
||||
const oldHash = this.spaceSnapshot[meta.name];
|
||||
const newHash = meta.lastModified;
|
||||
// Update in snapshot
|
||||
this.spaceSnapshot[meta.name] = newHash;
|
||||
|
||||
// Check what happened to the file
|
||||
if (
|
||||
(
|
||||
// New file scenario
|
||||
!oldHash && !this.initialFileListLoad
|
||||
) || (
|
||||
// Changed file scenario
|
||||
oldHash &&
|
||||
oldHash !== newHash
|
||||
)
|
||||
) {
|
||||
await this.dispatchEvent(
|
||||
"file:changed",
|
||||
meta.name,
|
||||
false,
|
||||
oldHash,
|
||||
newHash,
|
||||
);
|
||||
}
|
||||
// Page found, not deleted
|
||||
deletedFiles.delete(meta.name);
|
||||
}
|
||||
// Page found, not deleted
|
||||
deletedFiles.delete(meta.name);
|
||||
|
||||
// Update in snapshot
|
||||
this.spaceSnapshot[meta.name] = newHash;
|
||||
}
|
||||
for (const deletedFile of deletedFiles) {
|
||||
delete this.spaceSnapshot[deletedFile];
|
||||
await this.dispatchEvent("file:deleted", deletedFile);
|
||||
|
||||
for (const deletedFile of deletedFiles) {
|
||||
delete this.spaceSnapshot[deletedFile];
|
||||
await this.dispatchEvent("file:deleted", deletedFile);
|
||||
|
||||
if (deletedFile.endsWith(".md")) {
|
||||
const pageName = deletedFile.substring(0, deletedFile.length - 3);
|
||||
await this.dispatchEvent("page:deleted", pageName);
|
||||
if (deletedFile.endsWith(".md")) {
|
||||
const pageName = deletedFile.substring(0, deletedFile.length - 3);
|
||||
await this.dispatchEvent("page:deleted", pageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await this.dispatchEvent("file:listed", newFileList);
|
||||
this.alreadyFetching = false;
|
||||
this.initialFileListLoad = false;
|
||||
return newFileList;
|
||||
await this.dispatchEvent("file:listed", newFileList);
|
||||
this.initialFileListLoad = false;
|
||||
return newFileList;
|
||||
} finally {
|
||||
this.alreadyFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
async readFile(
|
||||
name: string,
|
||||
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
||||
const data = await this.wrapped.readFile(name);
|
||||
this.triggerEventsAndCache(name, data.meta.lastModified);
|
||||
return data;
|
||||
try {
|
||||
// Fetching mutex
|
||||
this.alreadyFetching = true;
|
||||
|
||||
// Fetch file
|
||||
const data = await this.wrapped.readFile(name);
|
||||
this.triggerEventsAndCache(name, data.meta.lastModified);
|
||||
return data;
|
||||
} finally {
|
||||
this.alreadyFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
async writeFile(
|
||||
@ -85,31 +105,36 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
||||
selfUpdate?: boolean,
|
||||
meta?: FileMeta,
|
||||
): Promise<FileMeta> {
|
||||
const newMeta = await this.wrapped.writeFile(
|
||||
name,
|
||||
data,
|
||||
selfUpdate,
|
||||
meta,
|
||||
);
|
||||
if (!selfUpdate) {
|
||||
await this.dispatchEvent("file:changed", name, true);
|
||||
}
|
||||
this.spaceSnapshot[name] = newMeta.lastModified;
|
||||
try {
|
||||
this.alreadyFetching = true;
|
||||
const newMeta = await this.wrapped.writeFile(
|
||||
name,
|
||||
data,
|
||||
selfUpdate,
|
||||
meta,
|
||||
);
|
||||
if (!selfUpdate) {
|
||||
await this.dispatchEvent("file:changed", name, true);
|
||||
}
|
||||
this.spaceSnapshot[name] = newMeta.lastModified;
|
||||
|
||||
if (name.endsWith(".md")) {
|
||||
// Let's trigger some page-specific events
|
||||
const pageName = name.substring(0, name.length - 3);
|
||||
let text = "";
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
text = decoder.decode(data);
|
||||
if (name.endsWith(".md")) {
|
||||
// Let's trigger some page-specific events
|
||||
const pageName = name.substring(0, name.length - 3);
|
||||
let text = "";
|
||||
const decoder = new TextDecoder("utf-8");
|
||||
text = decoder.decode(data);
|
||||
|
||||
await this.dispatchEvent("page:saved", pageName, newMeta);
|
||||
await this.dispatchEvent("page:index_text", {
|
||||
name: pageName,
|
||||
text,
|
||||
});
|
||||
await this.dispatchEvent("page:saved", pageName, newMeta);
|
||||
await this.dispatchEvent("page:index_text", {
|
||||
name: pageName,
|
||||
text,
|
||||
});
|
||||
}
|
||||
return newMeta;
|
||||
} finally {
|
||||
this.alreadyFetching = false;
|
||||
}
|
||||
return newMeta;
|
||||
}
|
||||
|
||||
triggerEventsAndCache(name: string, newHash: number) {
|
||||
@ -124,6 +149,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
||||
|
||||
async getFileMeta(name: string): Promise<FileMeta> {
|
||||
try {
|
||||
this.alreadyFetching = true;
|
||||
const newMeta = await this.wrapped.getFileMeta(name);
|
||||
this.triggerEventsAndCache(name, newMeta.lastModified);
|
||||
return newMeta;
|
||||
@ -137,17 +163,24 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
||||
}
|
||||
}
|
||||
throw e;
|
||||
} finally {
|
||||
this.alreadyFetching = false;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteFile(name: string): Promise<void> {
|
||||
if (name.endsWith(".md")) {
|
||||
const pageName = name.substring(0, name.length - 3);
|
||||
await this.dispatchEvent("page:deleted", pageName);
|
||||
try {
|
||||
this.alreadyFetching = true;
|
||||
if (name.endsWith(".md")) {
|
||||
const pageName = name.substring(0, name.length - 3);
|
||||
await this.dispatchEvent("page:deleted", pageName);
|
||||
}
|
||||
// await this.getPageMeta(name); // Check if page exists, if not throws Error
|
||||
await this.wrapped.deleteFile(name);
|
||||
delete this.spaceSnapshot[name];
|
||||
await this.dispatchEvent("file:deleted", name);
|
||||
} finally {
|
||||
this.alreadyFetching = false;
|
||||
}
|
||||
// await this.getPageMeta(name); // Check if page exists, if not throws Error
|
||||
await this.wrapped.deleteFile(name);
|
||||
delete this.spaceSnapshot[name];
|
||||
await this.dispatchEvent("file:deleted", name);
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,11 @@ export class PlugSpacePrimitives implements SpacePrimitives {
|
||||
for (const pm of await plug.invoke(name, [])) {
|
||||
allFiles.push(pm);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error listing files", e);
|
||||
} catch (e: any) {
|
||||
if (!e.message.includes("not available")) {
|
||||
// Don't report "not available in" environments errors
|
||||
console.error("Error listing files", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user