Expose file and page meta data directly

demo
Zef Hemel 2022-11-20 10:24:42 +01:00
parent 7a627c2f35
commit 89f27f9e9c
6 changed files with 114 additions and 40 deletions

View File

@ -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<FileMeta[]> {
const list = await this.wrapped.fetchFileList();
// Enrich the file list with custom meta data (for pages)
const allFilesMap: Map<string, any> = 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<FileMeta> {
return this.wrapped.getFileMeta(name);
}
writeFile(
name: string,
encoding: FileEncoding,
data: FileData,
selfUpdate?: boolean | undefined,
): Promise<FileMeta> {
return this.wrapped.writeFile(name, encoding, data, selfUpdate);
}
deleteFile(name: string): Promise<void> {
return this.wrapped.deleteFile(name);
}
// deno-lint-ignore no-explicit-any
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);
}
}

View File

@ -31,11 +31,7 @@ export class Space extends EventEmitter<SpaceEvents> {
newPageList.forEach((meta) => { newPageList.forEach((meta) => {
const pageName = meta.name; const pageName = meta.name;
const oldPageMeta = this.pageMetaCache.get(pageName); const oldPageMeta = this.pageMetaCache.get(pageName);
const newPageMeta: PageMeta = { const newPageMeta: PageMeta = { ...meta };
name: pageName,
lastModified: meta.lastModified,
perm: meta.perm,
};
if ( if (
!oldPageMeta && !oldPageMeta &&
(pageName.startsWith(plugPrefix) || !this.initialPageListLoad) (pageName.startsWith(plugPrefix) || !this.initialPageListLoad)

View File

@ -6,14 +6,14 @@ export type FileMeta = {
contentType: string; contentType: string;
size: number; size: number;
perm: "ro" | "rw"; perm: "ro" | "rw";
}; } & Record<string, any>;
export type PageMeta = { export type PageMeta = {
name: string; name: string;
lastModified: number; lastModified: number;
lastOpened?: number; lastOpened?: number;
perm: "ro" | "rw"; perm: "ro" | "rw";
}; } & Record<string, any>;
export type AttachmentMeta = { export type AttachmentMeta = {
name: string; name: string;

View File

@ -60,20 +60,7 @@ export async function indexLinks({ name, tree }: IndexTreeEvent) {
export async function pageQueryProvider({ export async function pageQueryProvider({
query, query,
}: QueryProviderEvent): Promise<any[]> { }: QueryProviderEvent): Promise<any[]> {
let allPages = await space.listPages(); return applyQuery(query, await space.listPages());
const allPageMap: Map<string, any> = 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);
} }
export async function linkQueryProvider({ export async function linkQueryProvider({

View File

@ -5,10 +5,18 @@ import { space } from "$sb/silverbullet-syscall/mod.ts";
import { niceDate } from "$sb/lib/dates.ts"; import { niceDate } from "$sb/lib/dates.ts";
const maxWidth = 70; 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 // Nicely format an array of JSON objects as a Markdown table
export function jsonToMDTable( export function jsonToMDTable(
jsonArray: any[], jsonArray: any[],
valueTransformer: (k: string, v: any) => string = (_k, v) => "" + v, valueTransformer: (k: string, v: any) => string = defaultJsonTransformer,
): string { ): string {
const fieldWidths = new Map<string, number>(); const fieldWidths = new Map<string, number>();
for (const entry of jsonArray) { for (const entry of jsonArray) {

View File

@ -25,7 +25,7 @@ import {
ensureTable as ensureStoreTable, ensureTable as ensureStoreTable,
storeSyscalls, storeSyscalls,
} from "../plugos/syscalls/store.deno.ts"; } 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 { PageNamespaceHook } from "./hooks/page_namespace.ts";
import { PlugSpacePrimitives } from "./hooks/plug_space_primitives.ts"; import { PlugSpacePrimitives } from "./hooks/plug_space_primitives.ts";
import { import {
@ -38,6 +38,7 @@ import { AssetBundlePlugSpacePrimitives } from "../common/spaces/asset_bundle_sp
import assetSyscalls from "../plugos/syscalls/asset.ts"; import assetSyscalls from "../plugos/syscalls/asset.ts";
import { AssetBundle } from "../plugos/asset_bundle/bundle.ts"; import { AssetBundle } from "../plugos/asset_bundle/bundle.ts";
import { AsyncSQLite } from "../plugos/sqlite/async_sqlite.ts"; import { AsyncSQLite } from "../plugos/sqlite/async_sqlite.ts";
import { FileMetaSpacePrimitives } from "../common/spaces/file_meta_space_primitives.ts";
export type ServerOptions = { export type ServerOptions = {
port: number; port: number;
@ -62,6 +63,7 @@ export class HttpServer {
abortController?: AbortController; abortController?: AbortController;
globalModules: Manifest; globalModules: Manifest;
assetBundle: AssetBundle; assetBundle: AssetBundle;
indexSyscalls: SysCallMapping;
constructor(options: ServerOptions) { constructor(options: ServerOptions) {
this.port = options.port; this.port = options.port;
@ -84,17 +86,28 @@ export class HttpServer {
const namespaceHook = new PageNamespaceHook(); const namespaceHook = new PageNamespaceHook();
this.system.addHook(namespaceHook); 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 // The space
try { try {
this.spacePrimitives = new AssetBundlePlugSpacePrimitives( this.spacePrimitives = new FileMetaSpacePrimitives(
new EventedSpacePrimitives( new AssetBundlePlugSpacePrimitives(
new PlugSpacePrimitives( new EventedSpacePrimitives(
new DiskSpacePrimitives(options.pagesPath), new PlugSpacePrimitives(
namespaceHook, new DiskSpacePrimitives(options.pagesPath),
namespaceHook,
),
this.eventHook,
), ),
this.eventHook, this.assetBundle,
), ),
this.assetBundle, this.indexSyscalls,
); );
this.space = new Space(this.spacePrimitives); this.space = new Space(this.spacePrimitives);
} catch (e: any) { } catch (e: any) {
@ -106,19 +119,13 @@ export class HttpServer {
Deno.exit(1); 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 // The cron hook
this.system.addHook(new DenoCronHook()); this.system.addHook(new DenoCronHook());
// Register syscalls available on the server side // Register syscalls available on the server side
this.system.registerSyscalls( this.system.registerSyscalls(
[], [],
pageIndexSyscalls(this.db), this.indexSyscalls,
storeSyscalls(this.db, "store"), storeSyscalls(this.db, "store"),
fullTextSearchSyscalls(this.db, "fts"), fullTextSearchSyscalls(this.db, "fts"),
spaceSyscalls(this.space), spaceSyscalls(this.space),
@ -308,10 +315,8 @@ export class HttpServer {
this.addPasswordAuth(fsRouter); this.addPasswordAuth(fsRouter);
// File list // File list
fsRouter.get("/", async ({ response }) => { fsRouter.get("/", async ({ response }) => {
const list = await spacePrimitives.fetchFileList();
// console.log("List", list);
response.headers.set("Content-type", "application/json"); response.headers.set("Content-type", "application/json");
response.body = JSON.stringify(list); response.body = JSON.stringify(await spacePrimitives.fetchFileList());
}); });
fsRouter fsRouter