From 89f27f9e9c6ca1ebf52c92999a08253279a2ada8 Mon Sep 17 00:00:00 2001 From: Zef Hemel Date: Sun, 20 Nov 2022 10:24:42 +0100 Subject: [PATCH] Expose file and page meta data directly --- common/spaces/file_meta_space_primitives.ts | 78 +++++++++++++++++++++ common/spaces/space.ts | 6 +- common/types.ts | 4 +- plugs/core/page.ts | 15 +--- plugs/directive/util.ts | 10 ++- server/http_server.ts | 41 ++++++----- 6 files changed, 114 insertions(+), 40 deletions(-) create mode 100644 common/spaces/file_meta_space_primitives.ts diff --git a/common/spaces/file_meta_space_primitives.ts b/common/spaces/file_meta_space_primitives.ts new file mode 100644 index 00000000..9cc010b9 --- /dev/null +++ b/common/spaces/file_meta_space_primitives.ts @@ -0,0 +1,78 @@ +import { Plug } from "../../plugos/plug.ts"; +import { FileMeta } from "../types.ts"; +import { FileData, FileEncoding, SpacePrimitives } from "./space_primitives.ts"; +import type { SysCallMapping } from "../../plugos/system.ts"; + +// Enriches the file list listing with custom metadata from the page index +export class FileMetaSpacePrimitives implements SpacePrimitives { + constructor( + private wrapped: SpacePrimitives, + private indexSyscalls: SysCallMapping, + ) { + } + + async fetchFileList(): Promise { + const list = await this.wrapped.fetchFileList(); + // Enrich the file list with custom meta data (for pages) + const allFilesMap: Map = new Map( + list.map((fm) => [fm.name, fm]), + ); + for ( + const { page, value } of await this.indexSyscalls["index.queryPrefix"]( + {} as any, + "meta:", + ) + ) { + const p = allFilesMap.get(`${page}.md`); + if (p) { + for (const [k, v] of Object.entries(value)) { + if ( + ["name", "lastModified", "size", "perm", "contentType"].includes(k) + ) { + continue; + } + p[k] = v; + } + } + } + return [...allFilesMap.values()]; + } + + readFile( + name: string, + encoding: FileEncoding, + ): Promise<{ data: FileData; meta: FileMeta }> { + return this.wrapped.readFile(name, encoding); + } + + getFileMeta(name: string): Promise { + return this.wrapped.getFileMeta(name); + } + + writeFile( + name: string, + encoding: FileEncoding, + data: FileData, + selfUpdate?: boolean | undefined, + ): Promise { + return this.wrapped.writeFile(name, encoding, data, selfUpdate); + } + + deleteFile(name: string): Promise { + return this.wrapped.deleteFile(name); + } + + // deno-lint-ignore no-explicit-any + proxySyscall(plug: Plug, name: string, args: any[]): Promise { + return this.wrapped.proxySyscall(plug, name, args); + } + + invokeFunction( + plug: Plug, + env: string, + name: string, + args: any[], + ): Promise { + return this.wrapped.invokeFunction(plug, env, name, args); + } +} diff --git a/common/spaces/space.ts b/common/spaces/space.ts index 6cec9ba1..1d62dbf7 100644 --- a/common/spaces/space.ts +++ b/common/spaces/space.ts @@ -31,11 +31,7 @@ export class Space extends EventEmitter { newPageList.forEach((meta) => { const pageName = meta.name; const oldPageMeta = this.pageMetaCache.get(pageName); - const newPageMeta: PageMeta = { - name: pageName, - lastModified: meta.lastModified, - perm: meta.perm, - }; + const newPageMeta: PageMeta = { ...meta }; if ( !oldPageMeta && (pageName.startsWith(plugPrefix) || !this.initialPageListLoad) diff --git a/common/types.ts b/common/types.ts index 79e3c396..97c38dde 100644 --- a/common/types.ts +++ b/common/types.ts @@ -6,14 +6,14 @@ export type FileMeta = { contentType: string; size: number; perm: "ro" | "rw"; -}; +} & Record; export type PageMeta = { name: string; lastModified: number; lastOpened?: number; perm: "ro" | "rw"; -}; +} & Record; export type AttachmentMeta = { name: string; diff --git a/plugs/core/page.ts b/plugs/core/page.ts index b5ffb752..e3c192fa 100644 --- a/plugs/core/page.ts +++ b/plugs/core/page.ts @@ -60,20 +60,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) { export async function pageQueryProvider({ query, }: QueryProviderEvent): Promise { - let allPages = await space.listPages(); - const allPageMap: Map = new Map( - allPages.map((pm) => [pm.name, pm]), - ); - for (const { page, value } of await index.queryPrefix("meta:")) { - const p = allPageMap.get(page); - if (p) { - for (const [k, v] of Object.entries(value)) { - p[k] = v; - } - } - } - allPages = [...allPageMap.values()]; - return applyQuery(query, allPages); + return applyQuery(query, await space.listPages()); } export async function linkQueryProvider({ diff --git a/plugs/directive/util.ts b/plugs/directive/util.ts index 59536c87..14718b17 100644 --- a/plugs/directive/util.ts +++ b/plugs/directive/util.ts @@ -5,10 +5,18 @@ import { space } from "$sb/silverbullet-syscall/mod.ts"; import { niceDate } from "$sb/lib/dates.ts"; const maxWidth = 70; + +export function defaultJsonTransformer(_k: string, v: any) { + if (v === undefined) { + return ""; + } + return "" + v; +} + // Nicely format an array of JSON objects as a Markdown table export function jsonToMDTable( jsonArray: any[], - valueTransformer: (k: string, v: any) => string = (_k, v) => "" + v, + valueTransformer: (k: string, v: any) => string = defaultJsonTransformer, ): string { const fieldWidths = new Map(); for (const entry of jsonArray) { diff --git a/server/http_server.ts b/server/http_server.ts index 85ea081a..c212d9cc 100644 --- a/server/http_server.ts +++ b/server/http_server.ts @@ -25,7 +25,7 @@ import { ensureTable as ensureStoreTable, storeSyscalls, } from "../plugos/syscalls/store.deno.ts"; -import { System } from "../plugos/system.ts"; +import { SysCallMapping, System } from "../plugos/system.ts"; import { PageNamespaceHook } from "./hooks/page_namespace.ts"; import { PlugSpacePrimitives } from "./hooks/plug_space_primitives.ts"; import { @@ -38,6 +38,7 @@ import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_sp import assetSyscalls from "../plugos/syscalls/asset.ts"; import { AssetBundle } from "../plugos/asset_bundle/bundle.ts"; import { AsyncSQLite } from "../plugos/sqlite/async_sqlite.ts"; +import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts"; export type ServerOptions = { port: number; @@ -62,6 +63,7 @@ export class HttpServer { abortController?: AbortController; globalModules: Manifest; assetBundle: AssetBundle; + indexSyscalls: SysCallMapping; constructor(options: ServerOptions) { this.port = options.port; @@ -84,17 +86,28 @@ export class HttpServer { const namespaceHook = new PageNamespaceHook(); this.system.addHook(namespaceHook); + // The database used for persistence (SQLite) + this.db = new AsyncSQLite(path.join(options.pagesPath, "data.db")); + this.db.init().catch((e) => { + console.error("Error initializing database", e); + }); + + this.indexSyscalls = pageIndexSyscalls(this.db); + // The space try { - this.spacePrimitives = new AssetBundlePlugSpacePrimitives( - new EventedSpacePrimitives( - new PlugSpacePrimitives( - new DiskSpacePrimitives(options.pagesPath), - namespaceHook, + this.spacePrimitives = new FileMetaSpacePrimitives( + new AssetBundlePlugSpacePrimitives( + new EventedSpacePrimitives( + new PlugSpacePrimitives( + new DiskSpacePrimitives(options.pagesPath), + namespaceHook, + ), + this.eventHook, ), - this.eventHook, + this.assetBundle, ), - this.assetBundle, + this.indexSyscalls, ); this.space = new Space(this.spacePrimitives); } catch (e: any) { @@ -106,19 +119,13 @@ export class HttpServer { Deno.exit(1); } - // The database used for persistence (SQLite) - this.db = new AsyncSQLite(path.join(options.pagesPath, "data.db")); - this.db.init().catch((e) => { - console.error("Error initializing database", e); - }); - // The cron hook this.system.addHook(new DenoCronHook()); // Register syscalls available on the server side this.system.registerSyscalls( [], - pageIndexSyscalls(this.db), + this.indexSyscalls, storeSyscalls(this.db, "store"), fullTextSearchSyscalls(this.db, "fts"), spaceSyscalls(this.space), @@ -308,10 +315,8 @@ export class HttpServer { this.addPasswordAuth(fsRouter); // File list fsRouter.get("/", async ({ response }) => { - const list = await spacePrimitives.fetchFileList(); - // console.log("List", list); response.headers.set("Content-type", "application/json"); - response.body = JSON.stringify(list); + response.body = JSON.stringify(await spacePrimitives.fetchFileList()); }); fsRouter