diff --git a/src/addon.ts b/src/addon.ts index 7b3bebd..a19e2ad 100644 --- a/src/addon.ts +++ b/src/addon.ts @@ -6,7 +6,8 @@ import hooks from "./hooks"; import api from "./api"; import { createZToolkit } from "./utils/ztoolkit"; import { MessageHelper } from "zotero-plugin-toolkit/dist/helpers/message"; -import type { handlers } from "./extras/parsingWorker"; +import type { handlers as parsingHandlers } from "./extras/parsingWorker"; +import type { handlers as relationHandlers } from "./extras/relationWorker"; class Addon { public data: { @@ -71,9 +72,10 @@ class Addon { }; relation: { worker?: Worker; + server?: MessageHelper; }; parsing: { - server?: MessageHelper; + server?: MessageHelper; }; imageCache: Record; hint: { diff --git a/src/extras/relationWorker.ts b/src/extras/relationWorker.ts index 8dfd1a1..1325ec5 100644 --- a/src/extras/relationWorker.ts +++ b/src/extras/relationWorker.ts @@ -1,4 +1,7 @@ import Dexie from "dexie"; +import { MessageHelper } from "zotero-plugin-toolkit"; + +export { handlers }; const db = new Dexie("BN_Two_Way_Relation") as Dexie & { link: Dexie.Table; @@ -10,12 +13,31 @@ db.version(2).stores({ annotation: "++id, fromLibID, fromKey, toLibID, toKey, url", }); -console.log("Using Dexie v" + Dexie.semVer, db); +log("Using Dexie v" + Dexie.semVer, db); -postMessage({ - type: "ready", +const funcs = { + addLink, + bulkAddLink, + rebuildLinkForNote, + getOutboundLinks, + getInboundLinks, + linkAnnotationToTarget, + getLinkTargetByAnnotation, + getAnnotationByLinkTarget, +}; + +const handlers = MessageHelper.wrapHandlers(funcs); + +const messageServer = new MessageHelper({ + canBeDestroyed: true, + dev: true, + name: "parsingWorker", + target: self, + handlers, }); +messageServer.start(); + async function addLink(model: LinkModel) { await db.link.add(model); } @@ -29,12 +51,12 @@ async function rebuildLinkForNote( fromKey: string, links: LinkModel[], ) { - console.log("rebuildLinkForNote", fromLibID, fromKey, links); + log("rebuildLinkForNote", fromLibID, fromKey, links); const collection = db.link.where({ fromLibID, fromKey }); const oldOutboundLinks = await collection.toArray(); await collection.delete().then((deleteCount) => { - console.log("Deleted " + deleteCount + " objects"); + log("Deleted " + deleteCount + " objects"); return bulkAddLink(links); }); return { @@ -43,17 +65,17 @@ async function rebuildLinkForNote( } async function getOutboundLinks(fromLibID: number, fromKey: string) { - console.log("getOutboundLinks", fromLibID, fromKey); + log("getOutboundLinks", fromLibID, fromKey); return db.link.where({ fromLibID, fromKey }).toArray(); } async function getInboundLinks(toLibID: number, toKey: string) { - console.log("getInboundLinks", toLibID, toKey); + log("getInboundLinks", toLibID, toKey); return db.link.where({ toLibID, toKey }).toArray(); } async function linkAnnotationToTarget(model: AnnotationModel) { - console.log("linkAnnotationToTarget", model); + log("linkAnnotationToTarget", model); const collection = db.annotation.where({ fromLibID: model.fromLibID, fromKey: model.fromKey, @@ -64,15 +86,19 @@ async function linkAnnotationToTarget(model: AnnotationModel) { } async function getLinkTargetByAnnotation(fromLibID: number, fromKey: string) { - console.log("getLinkTargetByAnnotation", fromLibID, fromKey); + log("getLinkTargetByAnnotation", fromLibID, fromKey); return db.annotation.get({ fromLibID, fromKey }); } async function getAnnotationByLinkTarget(toLibID: number, toKey: string) { - console.log("getAnnotationByLinkTarget", toLibID, toKey); + log("getAnnotationByLinkTarget", toLibID, toKey); return db.annotation.get({ toLibID, toKey }); } +function log(...args: any[]) { + if (__env__ === "development") console.log("[relationWorker]", ...args); +} + interface LinkModel { fromLibID: number; fromKey: string; @@ -91,73 +117,3 @@ interface AnnotationModel { toKey: string; url: string; } - -// Handle messages from the main thread and send responses back for await -onmessage = async (event) => { - const { type, jobID, data } = event.data; - console.log("Worker received message", type, jobID, data); - switch (type) { - case "addLink": - postMessage({ - type, - jobID, - result: await addLink(data), - }); - break; - case "bulkAddLink": - postMessage({ - type, - jobID, - result: await bulkAddLink(data), - }); - break; - case "rebuildLinkForNote": - postMessage({ - type, - jobID, - result: await rebuildLinkForNote( - data.fromLibID, - data.fromKey, - data.links, - ), - }); - break; - case "getOutboundLinks": - postMessage({ - type, - jobID, - result: await getOutboundLinks(data.fromLibID, data.fromKey), - }); - break; - case "getInboundLinks": - postMessage({ - type, - jobID, - result: await getInboundLinks(data.toLibID, data.toKey), - }); - break; - case "linkAnnotationToTarget": - postMessage({ - type, - jobID, - result: await linkAnnotationToTarget(data), - }); - break; - case "getLinkTargetByAnnotation": - postMessage({ - type, - jobID, - result: await getLinkTargetByAnnotation(data.fromLibID, data.fromKey), - }); - break; - case "getAnnotationByLinkTarget": - postMessage({ - type, - jobID, - result: await getAnnotationByLinkTarget(data.toLibID, data.toKey), - }); - break; - default: - break; - } -}; diff --git a/src/hooks.ts b/src/hooks.ts index 5a7c633..0412c27 100644 --- a/src/hooks.ts +++ b/src/hooks.ts @@ -42,7 +42,7 @@ import { patchViewItems } from "./modules/viewItems"; import { getFocusedWindow } from "./utils/window"; import { registerNoteRelation } from "./modules/workspace/relation"; import { getPref, setPref } from "./utils/prefs"; -import { closeRelationWorker } from "./utils/relation"; +import { closeRelationServer } from "./utils/relation"; import { registerNoteLinkSection } from "./modules/workspace/link"; import { showUserGuide } from "./modules/userGuide"; import { refreshTemplatesInNote } from "./modules/template/refresh"; @@ -109,7 +109,7 @@ async function onMainWindowUnload(win: Window): Promise { } function onShutdown(): void { - closeRelationWorker(); + closeRelationServer(); closeParsingServer(); ztoolkit.unregisterAll(); // Remove addon object diff --git a/src/utils/relation.ts b/src/utils/relation.ts index 6c11378..9e7b16c 100644 --- a/src/utils/relation.ts +++ b/src/utils/relation.ts @@ -1,73 +1,47 @@ +import { MessageHelper } from "zotero-plugin-toolkit"; import { config } from "../../package.json"; import { getNoteLinkParams } from "./link"; +import type { handlers } from "../extras/relationWorker"; + +function closeRelationServer() { + if (addon.data.relation.server) { + addon.data.relation.server.destroy(); + addon.data.relation.server = undefined; + } +} + +async function getRelationServer() { + if (!addon.data.relation.server) { + const worker = new Worker( + `chrome://${config.addonRef}/content/scripts/relationWorker.js`, + { name: "relationWorker" }, + ); + const server = new MessageHelper({ + canBeDestroyed: false, + dev: true, + name: "relationWorkerMain", + target: worker, + handlers: {}, + }); + server.start(); + await server.exec("_ping"); + addon.data.relation.server = server; + } + + return addon.data.relation.server!; +} + +export { getRelationServer, closeRelationServer }; export { updateNoteLinkRelation, getNoteLinkInboundRelation, getNoteLinkOutboundRelation, - closeRelationWorker, linkAnnotationToTarget, getLinkTargetByAnnotation, getAnnotationByLinkTarget, }; -function closeRelationWorker() { - if (addon.data.relation.worker) { - addon.data.relation.worker.terminate(); - addon.data.relation.worker = undefined; - } -} - -async function getRelationWorker() { - if (addon.data.relation.worker) { - return addon.data.relation.worker; - } - const deferred = Zotero.Promise.defer(); - const worker = new Worker( - `chrome://${config.addonRef}/content/scripts/relationWorker.js`, - ); - addon.data.relation.worker = worker; - worker.addEventListener( - "message", - (e) => { - if (e.data === "ready") { - ztoolkit.log("Relation worker is ready."); - deferred.resolve(); - } - }, - { once: true }, - ); - await deferred.promise; - return worker; -} - -async function executeRelationWorker(data: { - type: string; - data: any; -}): Promise { - const worker = await getRelationWorker(); - const deferred = Zotero.Promise.defer(); - const jobID = Zotero.Utilities.randomString(8); - let retData; - ztoolkit.log("executeRelationWorker", data, jobID); - const callback = (e: MessageEvent) => { - if (e.data.jobID === jobID) { - retData = e.data; - worker.removeEventListener("message", callback); - deferred.resolve(); - } - }; - worker.addEventListener("message", callback); - worker.postMessage({ ...data, jobID }); - await Promise.race([deferred.promise, Zotero.Promise.delay(5000)]); - if (!retData) { - worker.removeEventListener("message", callback); - throw new Error(`Worker timeout: ${data.type}, ${jobID}`); - } - ztoolkit.log("executeRelationWorker return", retData); - return (retData as { result: any }).result; -} - async function updateNoteLinkRelation(noteID: number) { const note = Zotero.Items.get(noteID); const affectedNoteIDs = new Set([noteID]); @@ -99,10 +73,10 @@ async function updateNoteLinkRelation(noteID: number) { } } } - const result = await executeRelationWorker({ - type: "rebuildLinkForNote", - data: { fromLibID, fromKey, links: linkToData }, - }); + const result = await ( + await getRelationServer() + ).exec("rebuildLinkForNote", [fromLibID, fromKey, linkToData]); + for (const link of result.oldOutboundLinks as LinkModel[]) { const item = Zotero.Items.getByLibraryAndKey(link.toLibID, link.toKey); if (!item) { @@ -120,28 +94,21 @@ async function updateNoteLinkRelation(noteID: number) { ); } -async function getNoteLinkOutboundRelation( - noteID: number, -): Promise { +async function getNoteLinkOutboundRelation(noteID: number) { const note = Zotero.Items.get(noteID); const fromLibID = note.libraryID; const fromKey = note.key; - return executeRelationWorker({ - type: "getOutboundLinks", - data: { fromLibID, fromKey }, - }); + return (await getRelationServer()).exec("getOutboundLinks", [ + fromLibID, + fromKey, + ]); } -async function getNoteLinkInboundRelation( - noteID: number, -): Promise { +async function getNoteLinkInboundRelation(noteID: number) { const note = Zotero.Items.get(noteID); const toLibID = note.libraryID; const toKey = note.key; - return executeRelationWorker({ - type: "getInboundLinks", - data: { toLibID, toKey }, - }); + return (await getRelationServer()).exec("getInboundLinks", [toLibID, toKey]); } function decodeHTMLEntities(text: string) { @@ -162,31 +129,22 @@ interface LinkModel { url: string; } -async function linkAnnotationToTarget(model: AnnotationModel): Promise { - return executeRelationWorker({ - type: "linkAnnotationToTarget", - data: model, - }); +async function linkAnnotationToTarget(model: AnnotationModel) { + return (await getRelationServer()).exec("linkAnnotationToTarget", [model]); } -async function getLinkTargetByAnnotation( - fromLibID: number, - fromKey: string, -): Promise { - return executeRelationWorker({ - type: "getLinkTargetByAnnotation", - data: { fromLibID, fromKey }, - }); +async function getLinkTargetByAnnotation(fromLibID: number, fromKey: string) { + return (await getRelationServer()).exec("getLinkTargetByAnnotation", [ + fromLibID, + fromKey, + ]); } -async function getAnnotationByLinkTarget( - toLibID: number, - toKey: string, -): Promise { - return executeRelationWorker({ - type: "getAnnotationByLinkTarget", - data: { toLibID, toKey }, - }); +async function getAnnotationByLinkTarget(toLibID: number, toKey: string) { + return (await getRelationServer()).exec("getAnnotationByLinkTarget", [ + toLibID, + toKey, + ]); } interface AnnotationModel { diff --git a/zotero-plugin.config.ts b/zotero-plugin.config.ts index 745c674..167a0bb 100644 --- a/zotero-plugin.config.ts +++ b/zotero-plugin.config.ts @@ -40,6 +40,9 @@ export default defineConfig({ }, { entryPoints: ["src/extras/*.*"], + define: { + __env__: `"${process.env.NODE_ENV}"`, + }, outdir: "build/addon/chrome/content/scripts", bundle: true, target: ["firefox115"],