Plugin stuff
This commit is contained in:
@@ -4,7 +4,7 @@ import * as path from "path";
|
||||
import { IndexApi } from "./index_api";
|
||||
import { PageApi } from "./page_api";
|
||||
import { SilverBulletHooks } from "../common/manifest";
|
||||
import pageIndexSyscalls from "./syscalls/page_index";
|
||||
import { pageIndexSyscalls } from "./syscalls/page_index";
|
||||
import { safeRun } from "./util";
|
||||
import { System } from "../plugos/system";
|
||||
|
||||
|
||||
+4
-11
@@ -1,7 +1,7 @@
|
||||
import { ApiProvider, ClientConnection } from "./api_server";
|
||||
import knex, { Knex } from "knex";
|
||||
import path from "path";
|
||||
import pageIndexSyscalls from "./syscalls/page_index";
|
||||
import { ensurePageIndexTable, pageIndexSyscalls } from "./syscalls/page_index";
|
||||
|
||||
type IndexItem = {
|
||||
page: string;
|
||||
@@ -10,7 +10,7 @@ type IndexItem = {
|
||||
};
|
||||
|
||||
export class IndexApi implements ApiProvider {
|
||||
db: Knex;
|
||||
db: Knex<any, unknown>;
|
||||
|
||||
constructor(rootPath: string) {
|
||||
this.db = knex({
|
||||
@@ -23,15 +23,7 @@ export class IndexApi implements ApiProvider {
|
||||
}
|
||||
|
||||
async init() {
|
||||
if (!(await this.db.schema.hasTable("page_index"))) {
|
||||
await this.db.schema.createTable("page_index", (table) => {
|
||||
table.string("page");
|
||||
table.string("key");
|
||||
table.text("value");
|
||||
table.primary(["page", "key"]);
|
||||
});
|
||||
console.log("Created table page_index");
|
||||
}
|
||||
await ensurePageIndexTable(this.db);
|
||||
}
|
||||
|
||||
api() {
|
||||
@@ -42,6 +34,7 @@ export class IndexApi implements ApiProvider {
|
||||
clientConn: ClientConnection,
|
||||
page: string
|
||||
) => {
|
||||
console.log("Now going to clear index for", page);
|
||||
return syscalls.clearPageIndexForPage(nullContext, page);
|
||||
},
|
||||
set: async (
|
||||
|
||||
+22
-4
@@ -12,6 +12,8 @@ import { Cursor, cursorEffect } from "../webapp/cursorEffect";
|
||||
import { SilverBulletHooks } from "../common/manifest";
|
||||
import { System } from "../plugos/system";
|
||||
import { EventFeature } from "../plugos/feature/event";
|
||||
import spaceSyscalls from "./syscalls/space";
|
||||
import { eventSyscalls } from "../plugos/syscall/event";
|
||||
|
||||
export class PageApi implements ApiProvider {
|
||||
openPages: Map<string, Page>;
|
||||
@@ -34,6 +36,8 @@ export class PageApi implements ApiProvider {
|
||||
this.system = system;
|
||||
this.eventFeature = new EventFeature();
|
||||
system.addFeature(this.eventFeature);
|
||||
system.registerSyscalls("space", [], spaceSyscalls(this));
|
||||
system.registerSyscalls("event", [], eventSyscalls(this.eventFeature));
|
||||
}
|
||||
|
||||
async init(): Promise<void> {
|
||||
@@ -225,7 +229,10 @@ export class PageApi implements ApiProvider {
|
||||
" to disk and indexing."
|
||||
);
|
||||
await this.flushPageToDisk(pageName, page);
|
||||
|
||||
await this.eventFeature.dispatchEvent(
|
||||
"page:saved",
|
||||
pageName
|
||||
);
|
||||
await this.eventFeature.dispatchEvent("page:index", {
|
||||
name: pageName,
|
||||
text: page.text.sliceString(0),
|
||||
@@ -293,21 +300,32 @@ export class PageApi implements ApiProvider {
|
||||
pageName: string,
|
||||
text: string
|
||||
) => {
|
||||
// Write to disk
|
||||
let pageMeta = await this.pageStore.writePage(pageName, text);
|
||||
|
||||
// Notify clients that have the page open
|
||||
let page = this.openPages.get(pageName);
|
||||
if (page) {
|
||||
for (let client of page.clientStates) {
|
||||
client.socket.emit("reloadPage", pageName);
|
||||
client.socket.emit("pageChanged", pageMeta);
|
||||
}
|
||||
this.openPages.delete(pageName);
|
||||
}
|
||||
return this.pageStore.writePage(pageName, text);
|
||||
// Trigger system events
|
||||
await this.eventFeature.dispatchEvent("page:saved", pageName);
|
||||
await this.eventFeature.dispatchEvent("page:index", {
|
||||
name: pageName,
|
||||
text: text,
|
||||
});
|
||||
return pageMeta;
|
||||
},
|
||||
|
||||
deletePage: async (clientConn: ClientConnection, pageName: string) => {
|
||||
this.openPages.delete(pageName);
|
||||
clientConn.openPages.delete(pageName);
|
||||
// Cascading of this to all connected clients will be handled by file watcher
|
||||
return this.pageStore.deletePage(pageName);
|
||||
await this.pageStore.deletePage(pageName);
|
||||
await this.eventFeature.dispatchEvent("page:deleted", pageName);
|
||||
},
|
||||
|
||||
listPages: async (clientConn: ClientConnection): Promise<PageMeta[]> => {
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Knex } from "knex";
|
||||
import { SysCallMapping } from "../../plugos/system";
|
||||
|
||||
import {
|
||||
ensureTable,
|
||||
storeReadSyscalls,
|
||||
storeWriteSyscalls,
|
||||
} from "../../plugos/syscall/store.knex_node";
|
||||
|
||||
type IndexItem = {
|
||||
page: string;
|
||||
key: string;
|
||||
@@ -12,72 +18,99 @@ export type KV = {
|
||||
value: any;
|
||||
};
|
||||
|
||||
export default function (db: Knex): SysCallMapping {
|
||||
/*
|
||||
Keyspace design:
|
||||
|
||||
for page lookups:
|
||||
p~page~key
|
||||
|
||||
for global lookups:
|
||||
k~key~page
|
||||
|
||||
*/
|
||||
|
||||
function pageKey(page: string, key: string) {
|
||||
return `p~${page}~${key}`;
|
||||
}
|
||||
|
||||
function unpackPageKey(dbKey: string): { page: string; key: string } {
|
||||
const [, page, key] = dbKey.split("~");
|
||||
return { page, key };
|
||||
}
|
||||
|
||||
function globalKey(page: string, key: string) {
|
||||
return `k~${key}~${page}`;
|
||||
}
|
||||
|
||||
function unpackGlobalKey(dbKey: string): { page: string; key: string } {
|
||||
const [, key, page] = dbKey.split("~");
|
||||
return { page, key };
|
||||
}
|
||||
|
||||
export async function ensurePageIndexTable(db: Knex<any, unknown>) {
|
||||
await ensureTable(db, "page_index");
|
||||
}
|
||||
|
||||
export function pageIndexSyscalls(db: Knex<any, unknown>): SysCallMapping {
|
||||
const readCalls = storeReadSyscalls(db, "page_index");
|
||||
const writeCalls = storeWriteSyscalls(db, "page_index");
|
||||
const apiObj: SysCallMapping = {
|
||||
clearPageIndexForPage: async (ctx, page: string) => {
|
||||
await db<IndexItem>("page_index").where({ page }).del();
|
||||
},
|
||||
set: async (ctx, page: string, key: string, value: any) => {
|
||||
let changed = await db<IndexItem>("page_index")
|
||||
.where({ page, key })
|
||||
.update("value", JSON.stringify(value));
|
||||
if (changed === 0) {
|
||||
await db<IndexItem>("page_index").insert({
|
||||
page,
|
||||
key,
|
||||
value: JSON.stringify(value),
|
||||
});
|
||||
}
|
||||
await writeCalls.set(ctx, pageKey(page, key), value);
|
||||
await writeCalls.set(ctx, globalKey(page, key), value);
|
||||
},
|
||||
batchSet: async (ctx, page: string, kvs: KV[]) => {
|
||||
for (let { key, value } of kvs) {
|
||||
await apiObj.set(ctx, page, key, value);
|
||||
}
|
||||
},
|
||||
get: async (ctx, page: string, key: string) => {
|
||||
let result = await db<IndexItem>("page_index")
|
||||
.where({ page, key })
|
||||
.select("value");
|
||||
if (result.length) {
|
||||
return JSON.parse(result[0].value);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
delete: async (ctx, page: string, key: string) => {
|
||||
await db<IndexItem>("page_index").where({ page, key }).del();
|
||||
await writeCalls.delete(ctx, pageKey(page, key));
|
||||
await writeCalls.delete(ctx, globalKey(page, key));
|
||||
},
|
||||
get: async (ctx, page: string, key: string) => {
|
||||
return readCalls.get(ctx, pageKey(page, key));
|
||||
},
|
||||
scanPrefixForPage: async (ctx, page: string, prefix: string) => {
|
||||
return (
|
||||
await db<IndexItem>("page_index")
|
||||
.where({ page })
|
||||
.andWhereLike("key", `${prefix}%`)
|
||||
.select("page", "key", "value")
|
||||
).map(({ page, key, value }) => ({
|
||||
page,
|
||||
key,
|
||||
value: JSON.parse(value),
|
||||
}));
|
||||
return (await readCalls.queryPrefix(ctx, pageKey(page, prefix))).map(
|
||||
({ key, value }: { key: string; value: any }) => {
|
||||
const { key: pageKey } = unpackPageKey(key);
|
||||
return {
|
||||
page,
|
||||
key: pageKey,
|
||||
value,
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
scanPrefixGlobal: async (ctx, prefix: string) => {
|
||||
return (
|
||||
await db<IndexItem>("page_index")
|
||||
.andWhereLike("key", `${prefix}%`)
|
||||
.select("page", "key", "value")
|
||||
).map(({ page, key, value }) => ({
|
||||
page,
|
||||
key,
|
||||
value: JSON.parse(value),
|
||||
}));
|
||||
return (await readCalls.queryPrefix(ctx, `k~${prefix}`)).map(
|
||||
({ key, value }: { key: string; value: any }) => {
|
||||
const { page, key: pageKey } = unpackGlobalKey(key);
|
||||
return {
|
||||
page,
|
||||
key: pageKey,
|
||||
value,
|
||||
};
|
||||
}
|
||||
);
|
||||
},
|
||||
clearPageIndexForPage: async (ctx, page: string) => {
|
||||
await apiObj.deletePrefixForPage(ctx, page, "");
|
||||
},
|
||||
deletePrefixForPage: async (ctx, page: string, prefix: string) => {
|
||||
return db<IndexItem>("page_index")
|
||||
.where({ page })
|
||||
.andWhereLike("key", `${prefix}%`)
|
||||
.del();
|
||||
// Collect all global keys for this page to delete
|
||||
let keysToDelete = (
|
||||
await readCalls.queryPrefix(ctx, pageKey(page, prefix))
|
||||
).map(({ key }: { key: string; value: string }) =>
|
||||
globalKey(page, unpackPageKey(key).key)
|
||||
);
|
||||
// Delete all page keys
|
||||
await writeCalls.deletePrefix(ctx, pageKey(page, prefix));
|
||||
await writeCalls.batchDelete(ctx, keysToDelete);
|
||||
},
|
||||
clearPageIndex: async () => {
|
||||
return db<IndexItem>("page_index").del();
|
||||
clearPageIndex: async (ctx) => {
|
||||
await writeCalls.deleteAll(ctx);
|
||||
},
|
||||
};
|
||||
return apiObj;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { PageMeta } from "../types";
|
||||
import { SysCallMapping } from "../../plugos/system";
|
||||
import { PageApi } from "../page_api";
|
||||
import { ClientConnection } from "../api_server";
|
||||
|
||||
export default (pageApi: PageApi): SysCallMapping => {
|
||||
const api = pageApi.api();
|
||||
// @ts-ignore
|
||||
const dummyConn = new ClientConnection(null);
|
||||
return {
|
||||
listPages: (ctx): Promise<PageMeta[]> => {
|
||||
return api.listPages(dummyConn);
|
||||
},
|
||||
readPage: async (
|
||||
ctx,
|
||||
name: string
|
||||
): Promise<{ text: string; meta: PageMeta }> => {
|
||||
return api.readPage(dummyConn, name);
|
||||
},
|
||||
writePage: async (ctx, name: string, text: string): Promise<PageMeta> => {
|
||||
return api.writePage(dummyConn, name, text);
|
||||
},
|
||||
deletePage: async (ctx, name: string) => {
|
||||
return api.deletePage(dummyConn, name);
|
||||
},
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user