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)
|
* - page:deleted (string)
|
||||||
*/
|
*/
|
||||||
export class EventedSpacePrimitives implements SpacePrimitives {
|
export class EventedSpacePrimitives implements SpacePrimitives {
|
||||||
|
// Avoid file listing while performing another fetch (read, write, meta) operation
|
||||||
alreadyFetching = false;
|
alreadyFetching = false;
|
||||||
|
|
||||||
initialFileListLoad = true;
|
initialFileListLoad = true;
|
||||||
|
|
||||||
spaceSnapshot: Record<string, number> = {};
|
spaceSnapshot: Record<string, number> = {};
|
||||||
@ -28,55 +30,73 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
|||||||
async fetchFileList(): Promise<FileMeta[]> {
|
async fetchFileList(): Promise<FileMeta[]> {
|
||||||
const newFileList = await this.wrapped.fetchFileList();
|
const newFileList = await this.wrapped.fetchFileList();
|
||||||
if (this.alreadyFetching) {
|
if (this.alreadyFetching) {
|
||||||
// Avoid race conditions
|
// Avoid race conditions, let's just skip this in terms of event triggering: that's ok
|
||||||
return newFileList;
|
return newFileList;
|
||||||
}
|
}
|
||||||
this.alreadyFetching = true;
|
try {
|
||||||
const deletedFiles = new Set<string>(Object.keys(this.spaceSnapshot));
|
this.alreadyFetching = true;
|
||||||
for (const meta of newFileList) {
|
const deletedFiles = new Set<string>(Object.keys(this.spaceSnapshot));
|
||||||
const oldHash = this.spaceSnapshot[meta.name];
|
for (const meta of newFileList) {
|
||||||
const newHash = meta.lastModified;
|
const oldHash = this.spaceSnapshot[meta.name];
|
||||||
if (
|
const newHash = meta.lastModified;
|
||||||
(
|
// Update in snapshot
|
||||||
// New file scenario
|
this.spaceSnapshot[meta.name] = newHash;
|
||||||
!oldHash && !this.initialFileListLoad
|
|
||||||
) || (
|
// Check what happened to the file
|
||||||
// Changed file scenario
|
if (
|
||||||
oldHash &&
|
(
|
||||||
oldHash !== newHash
|
// New file scenario
|
||||||
)
|
!oldHash && !this.initialFileListLoad
|
||||||
) {
|
) || (
|
||||||
await this.dispatchEvent("file:changed", meta.name);
|
// 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
|
for (const deletedFile of deletedFiles) {
|
||||||
this.spaceSnapshot[meta.name] = newHash;
|
delete this.spaceSnapshot[deletedFile];
|
||||||
}
|
await this.dispatchEvent("file:deleted", deletedFile);
|
||||||
|
|
||||||
for (const deletedFile of deletedFiles) {
|
if (deletedFile.endsWith(".md")) {
|
||||||
delete this.spaceSnapshot[deletedFile];
|
const pageName = deletedFile.substring(0, deletedFile.length - 3);
|
||||||
await this.dispatchEvent("file:deleted", deletedFile);
|
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);
|
await this.dispatchEvent("file:listed", newFileList);
|
||||||
this.alreadyFetching = false;
|
this.initialFileListLoad = false;
|
||||||
this.initialFileListLoad = false;
|
return newFileList;
|
||||||
return newFileList;
|
} finally {
|
||||||
|
this.alreadyFetching = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async readFile(
|
async readFile(
|
||||||
name: string,
|
name: string,
|
||||||
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
): Promise<{ data: Uint8Array; meta: FileMeta }> {
|
||||||
const data = await this.wrapped.readFile(name);
|
try {
|
||||||
this.triggerEventsAndCache(name, data.meta.lastModified);
|
// Fetching mutex
|
||||||
return data;
|
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(
|
async writeFile(
|
||||||
@ -85,31 +105,36 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
|||||||
selfUpdate?: boolean,
|
selfUpdate?: boolean,
|
||||||
meta?: FileMeta,
|
meta?: FileMeta,
|
||||||
): Promise<FileMeta> {
|
): Promise<FileMeta> {
|
||||||
const newMeta = await this.wrapped.writeFile(
|
try {
|
||||||
name,
|
this.alreadyFetching = true;
|
||||||
data,
|
const newMeta = await this.wrapped.writeFile(
|
||||||
selfUpdate,
|
name,
|
||||||
meta,
|
data,
|
||||||
);
|
selfUpdate,
|
||||||
if (!selfUpdate) {
|
meta,
|
||||||
await this.dispatchEvent("file:changed", name, true);
|
);
|
||||||
}
|
if (!selfUpdate) {
|
||||||
this.spaceSnapshot[name] = newMeta.lastModified;
|
await this.dispatchEvent("file:changed", name, true);
|
||||||
|
}
|
||||||
|
this.spaceSnapshot[name] = newMeta.lastModified;
|
||||||
|
|
||||||
if (name.endsWith(".md")) {
|
if (name.endsWith(".md")) {
|
||||||
// Let's trigger some page-specific events
|
// Let's trigger some page-specific events
|
||||||
const pageName = name.substring(0, name.length - 3);
|
const pageName = name.substring(0, name.length - 3);
|
||||||
let text = "";
|
let text = "";
|
||||||
const decoder = new TextDecoder("utf-8");
|
const decoder = new TextDecoder("utf-8");
|
||||||
text = decoder.decode(data);
|
text = decoder.decode(data);
|
||||||
|
|
||||||
await this.dispatchEvent("page:saved", pageName, newMeta);
|
await this.dispatchEvent("page:saved", pageName, newMeta);
|
||||||
await this.dispatchEvent("page:index_text", {
|
await this.dispatchEvent("page:index_text", {
|
||||||
name: pageName,
|
name: pageName,
|
||||||
text,
|
text,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
return newMeta;
|
||||||
|
} finally {
|
||||||
|
this.alreadyFetching = false;
|
||||||
}
|
}
|
||||||
return newMeta;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerEventsAndCache(name: string, newHash: number) {
|
triggerEventsAndCache(name: string, newHash: number) {
|
||||||
@ -124,6 +149,7 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
|||||||
|
|
||||||
async getFileMeta(name: string): Promise<FileMeta> {
|
async getFileMeta(name: string): Promise<FileMeta> {
|
||||||
try {
|
try {
|
||||||
|
this.alreadyFetching = true;
|
||||||
const newMeta = await this.wrapped.getFileMeta(name);
|
const newMeta = await this.wrapped.getFileMeta(name);
|
||||||
this.triggerEventsAndCache(name, newMeta.lastModified);
|
this.triggerEventsAndCache(name, newMeta.lastModified);
|
||||||
return newMeta;
|
return newMeta;
|
||||||
@ -137,17 +163,24 @@ export class EventedSpacePrimitives implements SpacePrimitives {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw e;
|
throw e;
|
||||||
|
} finally {
|
||||||
|
this.alreadyFetching = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteFile(name: string): Promise<void> {
|
async deleteFile(name: string): Promise<void> {
|
||||||
if (name.endsWith(".md")) {
|
try {
|
||||||
const pageName = name.substring(0, name.length - 3);
|
this.alreadyFetching = true;
|
||||||
await this.dispatchEvent("page:deleted", pageName);
|
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, [])) {
|
for (const pm of await plug.invoke(name, [])) {
|
||||||
allFiles.push(pm);
|
allFiles.push(pm);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
console.error("Error listing files", e);
|
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