@@ -0,0 +1,95 @@
|
||||
import { Plug } from "../../plugos/plug.ts";
|
||||
import { System } from "../../plugos/system.ts";
|
||||
import { Hook, Manifest } from "../../plugos/types.ts";
|
||||
|
||||
export type NamespaceOperation =
|
||||
| "readFile"
|
||||
| "writeFile"
|
||||
| "listFiles"
|
||||
| "getFileMeta"
|
||||
| "deleteFile";
|
||||
|
||||
export type PageNamespaceDef = {
|
||||
pattern: string;
|
||||
operation: NamespaceOperation;
|
||||
};
|
||||
|
||||
export type PageNamespaceHookT = {
|
||||
pageNamespace?: PageNamespaceDef;
|
||||
};
|
||||
|
||||
type SpaceFunction = {
|
||||
operation: NamespaceOperation;
|
||||
pattern: RegExp;
|
||||
plug: Plug<PageNamespaceHookT>;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export class PageNamespaceHook implements Hook<PageNamespaceHookT> {
|
||||
spaceFunctions: SpaceFunction[] = [];
|
||||
constructor() {}
|
||||
|
||||
apply(system: System<PageNamespaceHookT>): void {
|
||||
system.on({
|
||||
plugLoaded: () => {
|
||||
this.updateCache(system);
|
||||
},
|
||||
plugUnloaded: () => {
|
||||
this.updateCache(system);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
updateCache(system: System<PageNamespaceHookT>) {
|
||||
this.spaceFunctions = [];
|
||||
for (let plug of system.loadedPlugs.values()) {
|
||||
if (plug.manifest?.functions) {
|
||||
for (
|
||||
let [funcName, funcDef] of Object.entries(
|
||||
plug.manifest.functions,
|
||||
)
|
||||
) {
|
||||
if (funcDef.pageNamespace) {
|
||||
this.spaceFunctions.push({
|
||||
operation: funcDef.pageNamespace.operation,
|
||||
pattern: new RegExp(funcDef.pageNamespace.pattern),
|
||||
plug,
|
||||
name: funcName,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
validateManifest(manifest: Manifest<PageNamespaceHookT>): string[] {
|
||||
let errors: string[] = [];
|
||||
if (!manifest.functions) {
|
||||
return [];
|
||||
}
|
||||
for (let [funcName, funcDef] of Object.entries(manifest.functions)) {
|
||||
if (funcDef.pageNamespace) {
|
||||
if (!funcDef.pageNamespace.pattern) {
|
||||
errors.push(`Function ${funcName} has a namespace but no pattern`);
|
||||
}
|
||||
if (!funcDef.pageNamespace.operation) {
|
||||
errors.push(`Function ${funcName} has a namespace but no operation`);
|
||||
}
|
||||
if (
|
||||
![
|
||||
"readFile",
|
||||
"writeFile",
|
||||
"getFileMeta",
|
||||
"listFiles",
|
||||
"deleteFile",
|
||||
].includes(funcDef.pageNamespace.operation)
|
||||
) {
|
||||
errors.push(
|
||||
`Function ${funcName} has an invalid operation ${funcDef.pageNamespace.operation}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { Plug } from "../../plugos/plug.ts";
|
||||
import {
|
||||
FileData,
|
||||
FileEncoding,
|
||||
SpacePrimitives,
|
||||
} from "../../common/spaces/space_primitives.ts";
|
||||
import { AttachmentMeta, FileMeta, PageMeta } from "../../common/types.ts";
|
||||
import { NamespaceOperation, PageNamespaceHook } from "./page_namespace.ts";
|
||||
|
||||
export class PlugSpacePrimitives implements SpacePrimitives {
|
||||
constructor(
|
||||
private wrapped: SpacePrimitives,
|
||||
private hook: PageNamespaceHook,
|
||||
) {}
|
||||
|
||||
performOperation(
|
||||
type: NamespaceOperation,
|
||||
pageName: string,
|
||||
...args: any[]
|
||||
): Promise<any> | false {
|
||||
for (let { operation, pattern, plug, name } of this.hook.spaceFunctions) {
|
||||
if (operation === type && pageName.match(pattern)) {
|
||||
return plug.invoke(name, [pageName, ...args]);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async fetchFileList(): Promise<FileMeta[]> {
|
||||
let allFiles: FileMeta[] = [];
|
||||
for (let { plug, name, operation } of this.hook.spaceFunctions) {
|
||||
if (operation === "listFiles") {
|
||||
try {
|
||||
for (let pm of await plug.invoke(name, [])) {
|
||||
allFiles.push(pm);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error listing files", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
let result = await this.wrapped.fetchFileList();
|
||||
for (let pm of result) {
|
||||
allFiles.push(pm);
|
||||
}
|
||||
return allFiles;
|
||||
}
|
||||
|
||||
readFile(
|
||||
name: string,
|
||||
encoding: FileEncoding,
|
||||
): Promise<{ data: FileData; meta: FileMeta }> {
|
||||
let result = this.performOperation("readFile", name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return this.wrapped.readFile(name, encoding);
|
||||
}
|
||||
|
||||
getFileMeta(name: string): Promise<FileMeta> {
|
||||
let result = this.performOperation("getFileMeta", name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return this.wrapped.getFileMeta(name);
|
||||
}
|
||||
|
||||
writeFile(
|
||||
name: string,
|
||||
encoding: FileEncoding,
|
||||
data: FileData,
|
||||
selfUpdate?: boolean,
|
||||
): Promise<FileMeta> {
|
||||
let result = this.performOperation(
|
||||
"writeFile",
|
||||
name,
|
||||
encoding,
|
||||
data,
|
||||
selfUpdate,
|
||||
);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return this.wrapped.writeFile(name, encoding, data, selfUpdate);
|
||||
}
|
||||
|
||||
deleteFile(name: string): Promise<void> {
|
||||
let result = this.performOperation("deleteFile", name);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
return this.wrapped.deleteFile(name);
|
||||
}
|
||||
|
||||
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
||||
return this.wrapped.proxySyscall(plug, name, args);
|
||||
}
|
||||
|
||||
invokeFunction(
|
||||
plug: Plug<any>,
|
||||
env: string,
|
||||
name: string,
|
||||
args: any[],
|
||||
): Promise<any> {
|
||||
return this.wrapped.invokeFunction(plug, env, name, args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user