More fixes for service workers
This commit is contained in:
parent
fff2690e99
commit
e10f41031c
@ -152,27 +152,6 @@ export class ExpressServer {
|
|||||||
|
|
||||||
let plugRouter = express.Router();
|
let plugRouter = express.Router();
|
||||||
|
|
||||||
// Plug list
|
|
||||||
plugRouter.get("/", async (req, res) => {
|
|
||||||
res.json(
|
|
||||||
[...this.system.loadedPlugs.values()].map(({ name, version }) => ({
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
plugRouter.get("/:name", async (req, res) => {
|
|
||||||
const plugName = req.params.name;
|
|
||||||
const plug = this.system.loadedPlugs.get(plugName);
|
|
||||||
if (!plug) {
|
|
||||||
res.status(404);
|
|
||||||
res.send("Not found");
|
|
||||||
} else {
|
|
||||||
res.header("Last-Modified", "" + plug.version);
|
|
||||||
res.send(plug.manifest);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
plugRouter.post(
|
plugRouter.post(
|
||||||
"/:plug/syscall/:name",
|
"/:plug/syscall/:name",
|
||||||
bodyParser.json(),
|
bodyParser.json(),
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Editor } from "./editor";
|
import { Editor } from "./editor";
|
||||||
import { safeRun } from "./util";
|
import { safeRun } from "./util";
|
||||||
import { WatchableSpace } from "./spaces/cache_space";
|
import { Space } from "./spaces/space";
|
||||||
import { HttpRestSpace } from "./spaces/httprest_space";
|
import { HttpSpacePrimitives } from "./spaces/http_space_primitives";
|
||||||
import { IndexedDBSpace } from "./spaces/indexeddb_space";
|
import { IndexedDBSpacePrimitives } from "./spaces/indexeddb_space_primitives";
|
||||||
import { SpaceSync } from "./spaces/sync";
|
import { SpaceSync } from "./spaces/sync";
|
||||||
|
|
||||||
let localSpace = new WatchableSpace(new IndexedDBSpace("pages"), true);
|
let localSpace = new Space(new IndexedDBSpacePrimitives("pages"), true);
|
||||||
localSpace.watch();
|
localSpace.watch();
|
||||||
let serverSpace = new WatchableSpace(new HttpRestSpace(""), true);
|
let serverSpace = new Space(new HttpSpacePrimitives(""), true);
|
||||||
// serverSpace.watch();
|
serverSpace.watch();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
window.syncer = async () => {
|
window.syncer = async () => {
|
||||||
@ -28,7 +28,7 @@ window.syncer = async () => {
|
|||||||
localStorage.setItem("lastRemoteSync", "" + syncer.primaryLastSync);
|
localStorage.setItem("lastRemoteSync", "" + syncer.primaryLastSync);
|
||||||
console.log("Done!");
|
console.log("Done!");
|
||||||
};
|
};
|
||||||
let editor = new Editor(localSpace, document.getElementById("root")!);
|
let editor = new Editor(serverSpace, document.getElementById("root")!);
|
||||||
|
|
||||||
safeRun(async () => {
|
safeRun(async () => {
|
||||||
await editor.init();
|
await editor.init();
|
||||||
|
@ -29,7 +29,7 @@ import { PathPageNavigator } from "./navigator";
|
|||||||
import customMarkDown from "./parser";
|
import customMarkDown from "./parser";
|
||||||
import reducer from "./reducer";
|
import reducer from "./reducer";
|
||||||
import { smartQuoteKeymap } from "./smart_quotes";
|
import { smartQuoteKeymap } from "./smart_quotes";
|
||||||
import { WatchableSpace } from "./spaces/cache_space";
|
import { Space } from "./spaces/space";
|
||||||
import customMarkdownStyle from "./style";
|
import customMarkdownStyle from "./style";
|
||||||
import { editorSyscalls } from "./syscalls/editor";
|
import { editorSyscalls } from "./syscalls/editor";
|
||||||
import { indexerSyscalls } from "./syscalls";
|
import { indexerSyscalls } from "./syscalls";
|
||||||
@ -59,7 +59,7 @@ class PageState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveInterval = 2000;
|
const saveInterval = 1000;
|
||||||
|
|
||||||
export class Editor implements AppEventDispatcher {
|
export class Editor implements AppEventDispatcher {
|
||||||
readonly commandHook: CommandHook;
|
readonly commandHook: CommandHook;
|
||||||
@ -69,7 +69,7 @@ export class Editor implements AppEventDispatcher {
|
|||||||
editorView?: EditorView;
|
editorView?: EditorView;
|
||||||
viewState: AppViewState;
|
viewState: AppViewState;
|
||||||
viewDispatch: React.Dispatch<Action>;
|
viewDispatch: React.Dispatch<Action>;
|
||||||
space: WatchableSpace;
|
space: Space;
|
||||||
pageNavigator: PathPageNavigator;
|
pageNavigator: PathPageNavigator;
|
||||||
eventHook: EventHook;
|
eventHook: EventHook;
|
||||||
saveTimeout: any;
|
saveTimeout: any;
|
||||||
@ -78,7 +78,7 @@ export class Editor implements AppEventDispatcher {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
private system = new System<SilverBulletHooks>("client");
|
private system = new System<SilverBulletHooks>("client");
|
||||||
|
|
||||||
constructor(space: WatchableSpace, parent: Element) {
|
constructor(space: Space, parent: Element) {
|
||||||
this.space = space;
|
this.space = space;
|
||||||
this.viewState = initialViewState;
|
this.viewState = initialViewState;
|
||||||
this.viewDispatch = () => {};
|
this.viewDispatch = () => {};
|
||||||
|
@ -2,16 +2,43 @@ import { manifest, version } from "@parcel/service-worker";
|
|||||||
|
|
||||||
async function install() {
|
async function install() {
|
||||||
const cache = await caches.open(version);
|
const cache = await caches.open(version);
|
||||||
|
// console.log("Installing", manifest);
|
||||||
await cache.addAll(manifest);
|
await cache.addAll(manifest);
|
||||||
|
// console.log("DOne");
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
self.addEventListener("install", (e) => e.waitUntil(install()));
|
self.addEventListener("install", (e) => e.waitUntil(install()));
|
||||||
|
|
||||||
async function activate() {
|
async function activate() {
|
||||||
const keys = await caches.keys();
|
const keys = await caches.keys();
|
||||||
|
// console.log("Activating");
|
||||||
await Promise.all(keys.map((key) => key !== version && caches.delete(key)));
|
await Promise.all(keys.map((key) => key !== version && caches.delete(key)));
|
||||||
|
// console.log("DOne activating");
|
||||||
}
|
}
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
self.addEventListener("activate", (e) => e.waitUntil(activate()));
|
self.addEventListener("activate", (e) => e.waitUntil(activate()));
|
||||||
|
|
||||||
self.addEventListener("fetch", function (event) {});
|
self.addEventListener("fetch", (event: any) => {
|
||||||
|
event.respondWith(
|
||||||
|
caches.open(version).then(async (cache) => {
|
||||||
|
let parsedUrl = new URL(event.request.url);
|
||||||
|
// console.log("Got fetch request", parsedUrl.pathname);
|
||||||
|
let response = await cache.match(event.request, {
|
||||||
|
ignoreSearch: true,
|
||||||
|
});
|
||||||
|
// console.log("Got cache result", response);
|
||||||
|
if (response) {
|
||||||
|
return response;
|
||||||
|
} else {
|
||||||
|
if (
|
||||||
|
parsedUrl.pathname !== "/fs" &&
|
||||||
|
!parsedUrl.pathname.startsWith("/fs/") &&
|
||||||
|
!parsedUrl.pathname.startsWith("/plug/")
|
||||||
|
) {
|
||||||
|
return cache.match("/index.html");
|
||||||
|
}
|
||||||
|
return fetch(event.request);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -1,251 +0,0 @@
|
|||||||
import { Space, SpaceEvents } from "./space";
|
|
||||||
import { safeRun } from "../util";
|
|
||||||
import { PageMeta } from "../../common/types";
|
|
||||||
import { EventEmitter } from "../../common/event";
|
|
||||||
import { Plug } from "../../plugos/plug";
|
|
||||||
|
|
||||||
const pageWatchInterval = 2000;
|
|
||||||
const trashPrefix = "_trash/";
|
|
||||||
const plugPrefix = "_plug/";
|
|
||||||
|
|
||||||
export class WatchableSpace extends EventEmitter<SpaceEvents> {
|
|
||||||
pageMetaCache = new Map<string, PageMeta>();
|
|
||||||
watchedPages = new Set<string>();
|
|
||||||
private initialPageListLoad = true;
|
|
||||||
private saving = false;
|
|
||||||
|
|
||||||
constructor(private space: Space, private trashEnabled = true) {
|
|
||||||
super();
|
|
||||||
this.on({
|
|
||||||
pageCreated: async (pageMeta) => {
|
|
||||||
if (pageMeta.name.startsWith(plugPrefix)) {
|
|
||||||
let pageData = await this.readPage(pageMeta.name);
|
|
||||||
this.emit(
|
|
||||||
"plugLoaded",
|
|
||||||
pageMeta.name.substring(plugPrefix.length),
|
|
||||||
JSON.parse(pageData.text)
|
|
||||||
);
|
|
||||||
this.watchPage(pageMeta.name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
pageChanged: async (pageMeta) => {
|
|
||||||
if (pageMeta.name.startsWith(plugPrefix)) {
|
|
||||||
let pageData = await this.readPage(pageMeta.name);
|
|
||||||
this.emit(
|
|
||||||
"plugLoaded",
|
|
||||||
pageMeta.name.substring(plugPrefix.length),
|
|
||||||
JSON.parse(pageData.text)
|
|
||||||
);
|
|
||||||
this.watchPage(pageMeta.name);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public updatePageListAsync() {
|
|
||||||
safeRun(async () => {
|
|
||||||
let newPageList = await this.space.fetchPageList();
|
|
||||||
let deletedPages = new Set<string>(this.pageMetaCache.keys());
|
|
||||||
newPageList.pages.forEach((meta) => {
|
|
||||||
const pageName = meta.name;
|
|
||||||
const oldPageMeta = this.pageMetaCache.get(pageName);
|
|
||||||
const newPageMeta = {
|
|
||||||
name: pageName,
|
|
||||||
lastModified: meta.lastModified,
|
|
||||||
};
|
|
||||||
if (
|
|
||||||
!oldPageMeta &&
|
|
||||||
(pageName.startsWith(plugPrefix) || !this.initialPageListLoad)
|
|
||||||
) {
|
|
||||||
this.emit("pageCreated", newPageMeta);
|
|
||||||
} else if (
|
|
||||||
oldPageMeta &&
|
|
||||||
oldPageMeta.lastModified !== newPageMeta.lastModified
|
|
||||||
) {
|
|
||||||
this.emit("pageChanged", newPageMeta);
|
|
||||||
}
|
|
||||||
// Page found, not deleted
|
|
||||||
deletedPages.delete(pageName);
|
|
||||||
|
|
||||||
// Update in cache
|
|
||||||
this.pageMetaCache.set(pageName, newPageMeta);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const deletedPage of deletedPages) {
|
|
||||||
this.pageMetaCache.delete(deletedPage);
|
|
||||||
this.emit("pageDeleted", deletedPage);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit("pageListUpdated", this.listPages());
|
|
||||||
this.initialPageListLoad = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
watch() {
|
|
||||||
setInterval(() => {
|
|
||||||
safeRun(async () => {
|
|
||||||
if (this.saving) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
for (const pageName of this.watchedPages) {
|
|
||||||
const oldMeta = this.pageMetaCache.get(pageName);
|
|
||||||
if (!oldMeta) {
|
|
||||||
// No longer in cache, meaning probably deleted let's unwatch
|
|
||||||
this.watchedPages.delete(pageName);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const newMeta = await this.space.getPageMeta(pageName);
|
|
||||||
if (oldMeta.lastModified !== newMeta.lastModified) {
|
|
||||||
this.emit("pageChanged", newMeta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, pageWatchInterval);
|
|
||||||
this.updatePageListAsync();
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePage(name: string, deleteDate?: number): Promise<void> {
|
|
||||||
await this.getPageMeta(name); // Check if page exists, if not throws Error
|
|
||||||
if (this.trashEnabled) {
|
|
||||||
let pageData = await this.readPage(name);
|
|
||||||
// Move to trash
|
|
||||||
await this.writePage(
|
|
||||||
`${trashPrefix}${name}`,
|
|
||||||
pageData.text,
|
|
||||||
false,
|
|
||||||
deleteDate
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await this.space.deletePage(name);
|
|
||||||
|
|
||||||
this.pageMetaCache.delete(name);
|
|
||||||
this.emit("pageDeleted", name);
|
|
||||||
this.emit("pageListUpdated", new Set([...this.pageMetaCache.values()]));
|
|
||||||
}
|
|
||||||
|
|
||||||
async getPageMeta(name: string): Promise<PageMeta> {
|
|
||||||
return this.metaCacher(name, await this.space.getPageMeta(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
invokeFunction(
|
|
||||||
plug: Plug<any>,
|
|
||||||
env: string,
|
|
||||||
name: string,
|
|
||||||
args: any[]
|
|
||||||
): Promise<any> {
|
|
||||||
return this.space.invokeFunction(plug, env, name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
listPages(): Set<PageMeta> {
|
|
||||||
return new Set(
|
|
||||||
[...this.pageMetaCache.values()].filter(
|
|
||||||
(pageMeta) =>
|
|
||||||
!pageMeta.name.startsWith(trashPrefix) &&
|
|
||||||
!pageMeta.name.startsWith(plugPrefix)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
listTrash(): Set<PageMeta> {
|
|
||||||
return new Set(
|
|
||||||
[...this.pageMetaCache.values()]
|
|
||||||
.filter(
|
|
||||||
(pageMeta) =>
|
|
||||||
pageMeta.name.startsWith(trashPrefix) &&
|
|
||||||
!pageMeta.name.startsWith(plugPrefix)
|
|
||||||
)
|
|
||||||
.map((pageMeta) => ({
|
|
||||||
...pageMeta,
|
|
||||||
name: pageMeta.name.substring(trashPrefix.length),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
listPlugs(): Set<PageMeta> {
|
|
||||||
return new Set(
|
|
||||||
[...this.pageMetaCache.values()].filter((pageMeta) =>
|
|
||||||
pageMeta.name.startsWith(plugPrefix)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
|
||||||
return this.space.proxySyscall(plug, name, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
async readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
|
||||||
let pageData = await this.space.readPage(name);
|
|
||||||
this.pageMetaCache.set(name, pageData.meta);
|
|
||||||
return pageData;
|
|
||||||
}
|
|
||||||
|
|
||||||
watchPage(pageName: string) {
|
|
||||||
this.watchedPages.add(pageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
unwatchPage(pageName: string) {
|
|
||||||
this.watchedPages.delete(pageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
async writePage(
|
|
||||||
name: string,
|
|
||||||
text: string,
|
|
||||||
selfUpdate?: boolean,
|
|
||||||
lastModified?: number
|
|
||||||
): Promise<PageMeta> {
|
|
||||||
try {
|
|
||||||
this.saving = true;
|
|
||||||
let pageMeta = await this.space.writePage(
|
|
||||||
name,
|
|
||||||
text,
|
|
||||||
selfUpdate,
|
|
||||||
lastModified
|
|
||||||
);
|
|
||||||
if (!selfUpdate) {
|
|
||||||
this.emit("pageChanged", pageMeta);
|
|
||||||
}
|
|
||||||
return this.metaCacher(name, pageMeta);
|
|
||||||
} finally {
|
|
||||||
this.saving = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchPageList(): Promise<{ pages: Set<PageMeta>; nowTimestamp: number }> {
|
|
||||||
return this.space.fetchPageList();
|
|
||||||
}
|
|
||||||
|
|
||||||
// private pollPlugs() {
|
|
||||||
// safeRun(async () => {
|
|
||||||
// const newPlugs = await this.space.listPlugs();
|
|
||||||
// let deletedPlugs = new Set<string>(this.plugMetaCache.keys());
|
|
||||||
// for (const newPlugMeta of newPlugs) {
|
|
||||||
// const oldPlugMeta = this.plugMetaCache.get(newPlugMeta.name);
|
|
||||||
// if (
|
|
||||||
// !oldPlugMeta ||
|
|
||||||
// (oldPlugMeta && oldPlugMeta.version !== newPlugMeta.version)
|
|
||||||
// ) {
|
|
||||||
// this.emit(
|
|
||||||
// "plugLoaded",
|
|
||||||
// newPlugMeta.name,
|
|
||||||
// await this.space.loadPlug(newPlugMeta.name)
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// // Page found, not deleted
|
|
||||||
// deletedPlugs.delete(newPlugMeta.name);
|
|
||||||
//
|
|
||||||
// // Update in cache
|
|
||||||
// this.plugMetaCache.set(newPlugMeta.name, newPlugMeta);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for (const deletedPlug of deletedPlugs) {
|
|
||||||
// this.plugMetaCache.delete(deletedPlug);
|
|
||||||
// this.emit("plugUnloaded", deletedPlug);
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
private metaCacher(name: string, pageMeta: PageMeta): PageMeta {
|
|
||||||
this.pageMetaCache.set(name, pageMeta);
|
|
||||||
return pageMeta;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,8 +1,8 @@
|
|||||||
import { PageMeta } from "../../common/types";
|
import { PageMeta } from "../../common/types";
|
||||||
import { Plug } from "../../plugos/plug";
|
import { Plug } from "../../plugos/plug";
|
||||||
import { Space } from "./space";
|
import { SpacePrimitives } from "./space_primitives";
|
||||||
|
|
||||||
export class HttpRestSpace implements Space {
|
export class HttpSpacePrimitives implements SpacePrimitives {
|
||||||
pageUrl: string;
|
pageUrl: string;
|
||||||
private plugUrl: string;
|
private plugUrl: string;
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
import { Space } from "./space";
|
import { SpacePrimitives } from "./space_primitives";
|
||||||
import { PageMeta } from "../../common/types";
|
import { PageMeta } from "../../common/types";
|
||||||
import Dexie, { Table } from "dexie";
|
import Dexie, { Table } from "dexie";
|
||||||
import { Plug } from "../../plugos/plug";
|
import { Plug } from "../../plugos/plug";
|
||||||
@ -9,7 +9,7 @@ type Page = {
|
|||||||
meta: PageMeta;
|
meta: PageMeta;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class IndexedDBSpace implements Space {
|
export class IndexedDBSpacePrimitives implements SpacePrimitives {
|
||||||
private pageTable: Table<Page, string>;
|
private pageTable: Table<Page, string>;
|
||||||
|
|
||||||
constructor(dbName: string, readonly timeSkew: number = 0) {
|
constructor(dbName: string, readonly timeSkew: number = 0) {
|
||||||
@ -54,7 +54,6 @@ export class IndexedDBSpace implements Space {
|
|||||||
}
|
}
|
||||||
|
|
||||||
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
||||||
console.log("Going this", name);
|
|
||||||
return plug.syscall(name, args);
|
return plug.syscall(name, args);
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,13 @@
|
|||||||
import { Manifest } from "../../common/manifest";
|
import { SpacePrimitives } from "./space_primitives";
|
||||||
import { Plug } from "../../plugos/plug";
|
import { safeRun } from "../util";
|
||||||
import { PageMeta } from "../../common/types";
|
import { PageMeta } from "../../common/types";
|
||||||
|
import { EventEmitter } from "../../common/event";
|
||||||
|
import { Plug } from "../../plugos/plug";
|
||||||
|
import { Manifest } from "../../common/manifest";
|
||||||
|
|
||||||
|
const pageWatchInterval = 2000;
|
||||||
|
const trashPrefix = "_trash/";
|
||||||
|
const plugPrefix = "_plug/";
|
||||||
|
|
||||||
export type SpaceEvents = {
|
export type SpaceEvents = {
|
||||||
pageCreated: (meta: PageMeta) => void;
|
pageCreated: (meta: PageMeta) => void;
|
||||||
@ -11,25 +18,214 @@ export type SpaceEvents = {
|
|||||||
plugUnloaded: (plugName: string) => void;
|
plugUnloaded: (plugName: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface Space {
|
export class Space extends EventEmitter<SpaceEvents> {
|
||||||
// Pages
|
pageMetaCache = new Map<string, PageMeta>();
|
||||||
fetchPageList(): Promise<{ pages: Set<PageMeta>; nowTimestamp: number }>;
|
watchedPages = new Set<string>();
|
||||||
readPage(name: string): Promise<{ text: string; meta: PageMeta }>;
|
private initialPageListLoad = true;
|
||||||
getPageMeta(name: string): Promise<PageMeta>;
|
private saving = false;
|
||||||
writePage(
|
|
||||||
name: string,
|
constructor(private space: SpacePrimitives, private trashEnabled = true) {
|
||||||
text: string,
|
super();
|
||||||
selfUpdate?: boolean,
|
this.on({
|
||||||
lastModified?: number
|
pageCreated: async (pageMeta) => {
|
||||||
): Promise<PageMeta>;
|
if (pageMeta.name.startsWith(plugPrefix)) {
|
||||||
deletePage(name: string): Promise<void>;
|
let pageData = await this.readPage(pageMeta.name);
|
||||||
|
this.emit(
|
||||||
|
"plugLoaded",
|
||||||
|
pageMeta.name.substring(plugPrefix.length),
|
||||||
|
JSON.parse(pageData.text)
|
||||||
|
);
|
||||||
|
this.watchPage(pageMeta.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pageChanged: async (pageMeta) => {
|
||||||
|
if (pageMeta.name.startsWith(plugPrefix)) {
|
||||||
|
let pageData = await this.readPage(pageMeta.name);
|
||||||
|
this.emit(
|
||||||
|
"plugLoaded",
|
||||||
|
pageMeta.name.substring(plugPrefix.length),
|
||||||
|
JSON.parse(pageData.text)
|
||||||
|
);
|
||||||
|
this.watchPage(pageMeta.name);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public updatePageListAsync() {
|
||||||
|
safeRun(async () => {
|
||||||
|
let newPageList = await this.space.fetchPageList();
|
||||||
|
let deletedPages = new Set<string>(this.pageMetaCache.keys());
|
||||||
|
newPageList.pages.forEach((meta) => {
|
||||||
|
const pageName = meta.name;
|
||||||
|
const oldPageMeta = this.pageMetaCache.get(pageName);
|
||||||
|
const newPageMeta = {
|
||||||
|
name: pageName,
|
||||||
|
lastModified: meta.lastModified,
|
||||||
|
};
|
||||||
|
if (
|
||||||
|
!oldPageMeta &&
|
||||||
|
(pageName.startsWith(plugPrefix) || !this.initialPageListLoad)
|
||||||
|
) {
|
||||||
|
this.emit("pageCreated", newPageMeta);
|
||||||
|
} else if (
|
||||||
|
oldPageMeta &&
|
||||||
|
oldPageMeta.lastModified !== newPageMeta.lastModified
|
||||||
|
) {
|
||||||
|
this.emit("pageChanged", newPageMeta);
|
||||||
|
}
|
||||||
|
// Page found, not deleted
|
||||||
|
deletedPages.delete(pageName);
|
||||||
|
|
||||||
|
// Update in cache
|
||||||
|
this.pageMetaCache.set(pageName, newPageMeta);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const deletedPage of deletedPages) {
|
||||||
|
this.pageMetaCache.delete(deletedPage);
|
||||||
|
this.emit("pageDeleted", deletedPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit("pageListUpdated", this.listPages());
|
||||||
|
this.initialPageListLoad = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch() {
|
||||||
|
setInterval(() => {
|
||||||
|
safeRun(async () => {
|
||||||
|
if (this.saving) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const pageName of this.watchedPages) {
|
||||||
|
const oldMeta = this.pageMetaCache.get(pageName);
|
||||||
|
if (!oldMeta) {
|
||||||
|
// No longer in cache, meaning probably deleted let's unwatch
|
||||||
|
this.watchedPages.delete(pageName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const newMeta = await this.space.getPageMeta(pageName);
|
||||||
|
if (oldMeta.lastModified !== newMeta.lastModified) {
|
||||||
|
this.emit("pageChanged", newMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, pageWatchInterval);
|
||||||
|
this.updatePageListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePage(name: string, deleteDate?: number): Promise<void> {
|
||||||
|
await this.getPageMeta(name); // Check if page exists, if not throws Error
|
||||||
|
if (this.trashEnabled) {
|
||||||
|
let pageData = await this.readPage(name);
|
||||||
|
// Move to trash
|
||||||
|
await this.writePage(
|
||||||
|
`${trashPrefix}${name}`,
|
||||||
|
pageData.text,
|
||||||
|
false,
|
||||||
|
deleteDate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.space.deletePage(name);
|
||||||
|
|
||||||
|
this.pageMetaCache.delete(name);
|
||||||
|
this.emit("pageDeleted", name);
|
||||||
|
this.emit("pageListUpdated", new Set([...this.pageMetaCache.values()]));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPageMeta(name: string): Promise<PageMeta> {
|
||||||
|
return this.metaCacher(name, await this.space.getPageMeta(name));
|
||||||
|
}
|
||||||
|
|
||||||
// Plugs
|
|
||||||
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any>;
|
|
||||||
invokeFunction(
|
invokeFunction(
|
||||||
plug: Plug<any>,
|
plug: Plug<any>,
|
||||||
env: string,
|
env: string,
|
||||||
name: string,
|
name: string,
|
||||||
args: any[]
|
args: any[]
|
||||||
): Promise<any>;
|
): Promise<any> {
|
||||||
|
return this.space.invokeFunction(plug, env, name, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
listPages(): Set<PageMeta> {
|
||||||
|
return new Set(
|
||||||
|
[...this.pageMetaCache.values()].filter(
|
||||||
|
(pageMeta) =>
|
||||||
|
!pageMeta.name.startsWith(trashPrefix) &&
|
||||||
|
!pageMeta.name.startsWith(plugPrefix)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
listTrash(): Set<PageMeta> {
|
||||||
|
return new Set(
|
||||||
|
[...this.pageMetaCache.values()]
|
||||||
|
.filter(
|
||||||
|
(pageMeta) =>
|
||||||
|
pageMeta.name.startsWith(trashPrefix) &&
|
||||||
|
!pageMeta.name.startsWith(plugPrefix)
|
||||||
|
)
|
||||||
|
.map((pageMeta) => ({
|
||||||
|
...pageMeta,
|
||||||
|
name: pageMeta.name.substring(trashPrefix.length),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
listPlugs(): Set<PageMeta> {
|
||||||
|
return new Set(
|
||||||
|
[...this.pageMetaCache.values()].filter((pageMeta) =>
|
||||||
|
pageMeta.name.startsWith(plugPrefix)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any> {
|
||||||
|
return this.space.proxySyscall(plug, name, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async readPage(name: string): Promise<{ text: string; meta: PageMeta }> {
|
||||||
|
let pageData = await this.space.readPage(name);
|
||||||
|
this.pageMetaCache.set(name, pageData.meta);
|
||||||
|
return pageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
watchPage(pageName: string) {
|
||||||
|
this.watchedPages.add(pageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
unwatchPage(pageName: string) {
|
||||||
|
this.watchedPages.delete(pageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
async writePage(
|
||||||
|
name: string,
|
||||||
|
text: string,
|
||||||
|
selfUpdate?: boolean,
|
||||||
|
lastModified?: number
|
||||||
|
): Promise<PageMeta> {
|
||||||
|
try {
|
||||||
|
this.saving = true;
|
||||||
|
let pageMeta = await this.space.writePage(
|
||||||
|
name,
|
||||||
|
text,
|
||||||
|
selfUpdate,
|
||||||
|
lastModified
|
||||||
|
);
|
||||||
|
if (!selfUpdate) {
|
||||||
|
this.emit("pageChanged", pageMeta);
|
||||||
|
}
|
||||||
|
return this.metaCacher(name, pageMeta);
|
||||||
|
} finally {
|
||||||
|
this.saving = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPageList(): Promise<{ pages: Set<PageMeta>; nowTimestamp: number }> {
|
||||||
|
return this.space.fetchPageList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private metaCacher(name: string, pageMeta: PageMeta): PageMeta {
|
||||||
|
this.pageMetaCache.set(name, pageMeta);
|
||||||
|
return pageMeta;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
25
webapp/spaces/space_primitives.ts
Normal file
25
webapp/spaces/space_primitives.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Plug } from "../../plugos/plug";
|
||||||
|
import { PageMeta } from "../../common/types";
|
||||||
|
|
||||||
|
export interface SpacePrimitives {
|
||||||
|
// Pages
|
||||||
|
fetchPageList(): Promise<{ pages: Set<PageMeta>; nowTimestamp: number }>;
|
||||||
|
readPage(name: string): Promise<{ text: string; meta: PageMeta }>;
|
||||||
|
getPageMeta(name: string): Promise<PageMeta>;
|
||||||
|
writePage(
|
||||||
|
name: string,
|
||||||
|
text: string,
|
||||||
|
selfUpdate?: boolean,
|
||||||
|
lastModified?: number
|
||||||
|
): Promise<PageMeta>;
|
||||||
|
deletePage(name: string): Promise<void>;
|
||||||
|
|
||||||
|
// Plugs
|
||||||
|
proxySyscall(plug: Plug<any>, name: string, args: any[]): Promise<any>;
|
||||||
|
invokeFunction(
|
||||||
|
plug: Plug<any>,
|
||||||
|
env: string,
|
||||||
|
name: string,
|
||||||
|
args: any[]
|
||||||
|
): Promise<any>;
|
||||||
|
}
|
@ -1,16 +1,16 @@
|
|||||||
import { expect, test } from "@jest/globals";
|
import { expect, test } from "@jest/globals";
|
||||||
import { IndexedDBSpace } from "./indexeddb_space";
|
import { IndexedDBSpacePrimitives } from "./indexeddb_space_primitives";
|
||||||
import { SpaceSync } from "./sync";
|
import { SpaceSync } from "./sync";
|
||||||
import { PageMeta } from "../../common/types";
|
import { PageMeta } from "../../common/types";
|
||||||
import { WatchableSpace } from "./cache_space";
|
import { Space } from "./space";
|
||||||
|
|
||||||
// For testing in node.js
|
// For testing in node.js
|
||||||
require("fake-indexeddb/auto");
|
require("fake-indexeddb/auto");
|
||||||
|
|
||||||
test("Test store", async () => {
|
test("Test store", async () => {
|
||||||
let primary = new WatchableSpace(new IndexedDBSpace("primary"), true);
|
let primary = new Space(new IndexedDBSpacePrimitives("primary"), true);
|
||||||
let secondary = new WatchableSpace(
|
let secondary = new Space(
|
||||||
new IndexedDBSpace("secondary", -5000),
|
new IndexedDBSpacePrimitives("secondary", -5000),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
let sync = new SpaceSync(primary, secondary, 0, 0, "_trash/");
|
let sync = new SpaceSync(primary, secondary, 0, 0, "_trash/");
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { WatchableSpace } from "./cache_space";
|
|
||||||
import { PageMeta } from "../../common/types";
|
|
||||||
import { Space } from "./space";
|
import { Space } from "./space";
|
||||||
|
import { PageMeta } from "../../common/types";
|
||||||
|
import { SpacePrimitives } from "./space_primitives";
|
||||||
|
|
||||||
export class SpaceSync {
|
export class SpaceSync {
|
||||||
constructor(
|
constructor(
|
||||||
private primary: WatchableSpace,
|
private primary: Space,
|
||||||
private secondary: WatchableSpace,
|
private secondary: Space,
|
||||||
public primaryLastSync: number,
|
public primaryLastSync: number,
|
||||||
public secondaryLastSync: number,
|
public secondaryLastSync: number,
|
||||||
private trashPrefix: string
|
private trashPrefix: string
|
||||||
@ -13,8 +13,8 @@ export class SpaceSync {
|
|||||||
|
|
||||||
// Strategy: Primary wins
|
// Strategy: Primary wins
|
||||||
public static primaryConflictResolver(
|
public static primaryConflictResolver(
|
||||||
primary: WatchableSpace,
|
primary: Space,
|
||||||
secondary: WatchableSpace
|
secondary: Space
|
||||||
): (pageMeta1: PageMeta, pageMeta2: PageMeta) => Promise<void> {
|
): (pageMeta1: PageMeta, pageMeta2: PageMeta) => Promise<void> {
|
||||||
return async (pageMeta1, pageMeta2) => {
|
return async (pageMeta1, pageMeta2) => {
|
||||||
const pageName = pageMeta1.name;
|
const pageName = pageMeta1.name;
|
||||||
@ -35,7 +35,7 @@ export class SpaceSync {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async syncablePages(
|
async syncablePages(
|
||||||
space: WatchableSpace
|
space: Space
|
||||||
): Promise<{ pages: PageMeta[]; nowTimestamp: number }> {
|
): Promise<{ pages: PageMeta[]; nowTimestamp: number }> {
|
||||||
let fetchResult = await space.fetchPageList();
|
let fetchResult = await space.fetchPageList();
|
||||||
return {
|
return {
|
||||||
@ -46,7 +46,7 @@ export class SpaceSync {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async trashPages(space: Space): Promise<PageMeta[]> {
|
async trashPages(space: SpacePrimitives): Promise<PageMeta[]> {
|
||||||
return [...(await space.fetchPageList()).pages]
|
return [...(await space.fetchPageList()).pages]
|
||||||
.filter((pageMeta) => pageMeta.name.startsWith(this.trashPrefix))
|
.filter((pageMeta) => pageMeta.name.startsWith(this.trashPrefix))
|
||||||
.map((pageMeta) => ({
|
.map((pageMeta) => ({
|
||||||
@ -203,20 +203,6 @@ export class SpaceSync {
|
|||||||
this.primaryLastSync = primarySyncTimestamp;
|
this.primaryLastSync = primarySyncTimestamp;
|
||||||
this.secondaryLastSync = secondarySyncTimestamp;
|
this.secondaryLastSync = secondarySyncTimestamp;
|
||||||
|
|
||||||
// Find the latest timestamp and set it as lastSync
|
|
||||||
// allPagesPrimary.forEach((pageMeta) => {
|
|
||||||
// this.lastSync = Math.max(this.lastSync, pageMeta.lastModified);
|
|
||||||
// });
|
|
||||||
// allPagesSecondary.forEach((pageMeta) => {
|
|
||||||
// this.lastSync = Math.max(this.lastSync, pageMeta.lastModified);
|
|
||||||
// });
|
|
||||||
// allTrashPrimary.forEach((pageMeta) => {
|
|
||||||
// this.lastSync = Math.max(this.lastSync, pageMeta.lastModified);
|
|
||||||
// });
|
|
||||||
// allTrashSecondary.forEach((pageMeta) => {
|
|
||||||
// this.lastSync = Math.max(this.lastSync, pageMeta.lastModified);
|
|
||||||
// });
|
|
||||||
|
|
||||||
return syncOps;
|
return syncOps;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { SysCallMapping } from "../../plugos/system";
|
import { SysCallMapping } from "../../plugos/system";
|
||||||
import { proxySyscalls } from "../../plugos/syscalls/transport";
|
import { proxySyscalls } from "../../plugos/syscalls/transport";
|
||||||
import { WatchableSpace } from "../spaces/cache_space";
|
import { Space } from "../spaces/space";
|
||||||
|
|
||||||
export function indexerSyscalls(space: WatchableSpace): SysCallMapping {
|
export function indexerSyscalls(space: Space): SysCallMapping {
|
||||||
return proxySyscalls(
|
return proxySyscalls(
|
||||||
[
|
[
|
||||||
"index.scanPrefixForPage",
|
"index.scanPrefixForPage",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { SysCallMapping } from "../../plugos/system";
|
import { SysCallMapping } from "../../plugos/system";
|
||||||
import { WatchableSpace } from "../spaces/cache_space";
|
import { Space } from "../spaces/space";
|
||||||
|
|
||||||
export function systemSyscalls(space: WatchableSpace): SysCallMapping {
|
export function systemSyscalls(space: Space): SysCallMapping {
|
||||||
return {
|
return {
|
||||||
"system.invokeFunction": async (
|
"system.invokeFunction": async (
|
||||||
ctx,
|
ctx,
|
||||||
|
Loading…
Reference in New Issue
Block a user