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) => {
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)

View File

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

View File

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

View File

@ -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,9 +86,18 @@ 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(
this.spacePrimitives = new FileMetaSpacePrimitives(
new AssetBundlePlugSpacePrimitives(
new EventedSpacePrimitives(
new PlugSpacePrimitives(
new DiskSpacePrimitives(options.pagesPath),
@ -95,6 +106,8 @@ export class HttpServer {
this.eventHook,
),
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