Skip to content

Commit

Permalink
Auto reload dev plugins when plugin src file changes
Browse files Browse the repository at this point in the history
  • Loading branch information
qianlifeng committed Dec 28, 2023
1 parent b08bb51 commit f67d189
Show file tree
Hide file tree
Showing 9 changed files with 255 additions and 118 deletions.
1 change: 0 additions & 1 deletion Wox.Plugin.Host.Nodejs/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ logger.info(`wox pid: ${woxPid}`)
setInterval(() => {
try {
process.kill(Number.parseInt(woxPid), 0)
logger.error(`wox process is alive`)
} catch (e) {
logger.error(`wox process is not alive, exit`)
process.exit(1)
Expand Down
92 changes: 62 additions & 30 deletions Wox.Plugin.Host.Nodejs/src/jsonrpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@ import { Plugin, PluginInitContext, Query, RefreshableResult, Result, ResultActi
import { WebSocket } from "ws"
import * as crypto from "crypto"

const pluginMap = new Map<string, Plugin>()
const actionCacheByPlugin = new Map<PluginJsonRpcRequest["PluginId"], Map<Result["Id"], ResultAction["Action"]>>()
const refreshCacheByPlugin = new Map<PluginJsonRpcRequest["PluginId"], Map<Result["Id"], Result["OnRefresh"]>>()
const pluginApiMap = new Map<PluginJsonRpcRequest["PluginId"], PluginAPI>()
const pluginInstances = new Map<PluginJsonRpcRequest["PluginId"], PluginInstance>()

export const PluginJsonRpcTypeRequest: string = "WOX_JSONRPC_REQUEST"
export const PluginJsonRpcTypeResponse: string = "WOX_JSONRPC_RESPONSE"

export interface PluginInstance {
Plugin: Plugin
API: PluginAPI
ModulePath: string
Actions: Map<Result["Id"], ResultAction["Action"]>
Refreshes: Map<Result["Id"], Result["OnRefresh"]>
}

export interface PluginJsonRpcRequest {
Id: string
PluginId: string
Expand Down Expand Up @@ -70,23 +75,36 @@ async function loadPlugin(request: PluginJsonRpcRequest) {
}

logger.info(`[${request.PluginName}] load plugin successfully`)
pluginMap.set(request.PluginId, module["plugin"] as Plugin)
pluginInstances.set(request.PluginId, {
Plugin: module["plugin"] as Plugin,
API: {} as PluginAPI,
ModulePath: modulePath,
Actions: new Map<Result["Id"], ResultAction["Action"]>(),
Refreshes: new Map<Result["Id"], Result["OnRefresh"]>()
})
}

function unloadPlugin(request: PluginJsonRpcRequest) {
pluginMap.delete(request.PluginId)
actionCacheByPlugin.delete(request.PluginId)
let pluginInstance = pluginInstances.get(request.PluginId)
if (pluginInstance === undefined || pluginInstance === null) {
logger.error(`[${request.PluginName}] plugin instance not found: ${request.PluginName}`)
throw new Error(`plugin instance not found: ${request.PluginName}`)
}

delete require.cache[require.resolve(pluginInstance.ModulePath)]
pluginInstances.delete(request.PluginId)

logger.info(`[${request.PluginName}] unload plugin successfully`)
}

function getMethod<M extends keyof Plugin>(request: PluginJsonRpcRequest, methodName: M): Plugin[M] {
const plugin = pluginMap.get(request.PluginId)
const plugin = pluginInstances.get(request.PluginId)
if (plugin === undefined || plugin === null) {
logger.error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
throw new Error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
}

const method = plugin[methodName]
const method = plugin.Plugin[methodName]
if (method === undefined) {
logger.info(`plugin method not found: ${request.PluginName}`)
throw new Error(`plugin method not found: ${request.PluginName}`)
Expand All @@ -96,28 +114,43 @@ function getMethod<M extends keyof Plugin>(request: PluginJsonRpcRequest, method
}

async function initPlugin(request: PluginJsonRpcRequest, ws: WebSocket) {
const plugin = pluginInstances.get(request.PluginId)
if (plugin === undefined || plugin === null) {
logger.error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
throw new Error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
}

const init = getMethod(request, "init")
const pluginApi = new PluginAPI(ws, request.PluginId, request.PluginName)
pluginApiMap.set(request.PluginId, pluginApi)
plugin.API = pluginApi
return init({ API: pluginApi, PluginDirectory: request.Params.PluginDirectory } as PluginInitContext)
}

async function onPluginSettingChange(request: PluginJsonRpcRequest) {
const plugin = pluginInstances.get(request.PluginId)
if (plugin === undefined || plugin === null) {
logger.error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
throw new Error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
}

const settingKey = request.Params.Key
const settingValue = request.Params.Value
const callbackId = request.Params.CallbackId
pluginApiMap.get(request.PluginId)?.settingChangeCallbacks.get(callbackId)?.(settingKey, settingValue)
plugin.API.settingChangeCallbacks.get(callbackId)?.(settingKey, settingValue)
}

async function query(request: PluginJsonRpcRequest) {
const plugin = pluginInstances.get(request.PluginId)
if (plugin === undefined || plugin === null) {
logger.error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
throw new Error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
}

const query = getMethod(request, "query")

//clean action cache for current plugin
actionCacheByPlugin.set(request.PluginId, new Map<Result["Id"], ResultAction["Action"]>())
refreshCacheByPlugin.set(request.PluginId, new Map<Result["Id"], Result["OnRefresh"]>())

const actionCache = actionCacheByPlugin.get(request.PluginId)!
const refreshCache = refreshCacheByPlugin.get(request.PluginId)!
plugin.Actions.clear()
plugin.Refreshes.clear()

const results = await query({
Type: request.Params.Type,
Expand All @@ -143,15 +176,15 @@ async function query(request: PluginJsonRpcRequest) {
if (action.Id === undefined || action.Id === null) {
action.Id = crypto.randomUUID()
}
actionCache.set(action.Id, action.Action)
plugin.Actions.set(action.Id, action.Action)
})
}
if (result.RefreshInterval === undefined || result.RefreshInterval === null) {
result.RefreshInterval = 0
}
if (result.RefreshInterval > 0) {
if (result.OnRefresh !== undefined && result.OnRefresh !== null) {
refreshCache.set(result.Id, result.OnRefresh)
plugin.Refreshes.set(result.Id, result.OnRefresh)
}
}
})
Expand All @@ -160,13 +193,13 @@ async function query(request: PluginJsonRpcRequest) {
}

async function action(request: PluginJsonRpcRequest) {
const pluginActionCache = actionCacheByPlugin.get(request.PluginId)
if (pluginActionCache === undefined || pluginActionCache === null) {
logger.error(`[${request.PluginName}] plugin action cache not found: ${request.PluginName}`)
return
const plugin = pluginInstances.get(request.PluginId)
if (plugin === undefined || plugin === null) {
logger.error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
throw new Error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
}

const pluginAction = pluginActionCache.get(request.Params.ActionId)
const pluginAction = plugin.Actions.get(request.Params.ActionId)
if (pluginAction === undefined || pluginAction === null) {
logger.error(`[${request.PluginName}] plugin action not found: ${request.PluginName}`)
return
Expand All @@ -178,19 +211,18 @@ async function action(request: PluginJsonRpcRequest) {
}

async function refresh(request: PluginJsonRpcRequest) {
const pluginRefreshCache = refreshCacheByPlugin.get(request.PluginId)
if (pluginRefreshCache === undefined || pluginRefreshCache === null) {
logger.error(`[${request.PluginName}] plugin refresh cache not found: ${request.PluginName}`)
return
const plugin = pluginInstances.get(request.PluginId)
if (plugin === undefined || plugin === null) {
logger.error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
throw new Error(`plugin not found: ${request.PluginName}, forget to load plugin?`)
}

const result = JSON.parse(request.Params.RefreshableResult) as RefreshableResult

const pluginRefresh = pluginRefreshCache.get(request.Params.ResultId)
const pluginRefresh = plugin.Refreshes.get(request.Params.ResultId)
if (pluginRefresh === undefined || pluginRefresh === null) {
logger.error(`[${request.PluginName}] plugin refresh not found: ${request.PluginName}`)
return
}

const result = JSON.parse(request.Params.RefreshableResult) as RefreshableResult
return await pluginRefresh(result)
}
2 changes: 2 additions & 0 deletions Wox/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package plugin

import (
"context"
"fmt"
"github.com/samber/lo"
"path"
"wox/i18n"
Expand Down Expand Up @@ -49,6 +50,7 @@ func (a *APIImpl) ShowMsg(ctx context.Context, title string, description string,

func (a *APIImpl) Log(ctx context.Context, msg string) {
a.logger.Info(ctx, msg)
logger.Info(ctx, fmt.Sprintf("[%s] %s", a.pluginInstance.Metadata.Name, msg))
}

func (a *APIImpl) GetTranslation(ctx context.Context, key string) string {
Expand Down
28 changes: 28 additions & 0 deletions Wox/plugin/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,34 @@ func (m *Manager) loadPlugins(ctx context.Context) error {
return nil
}

func (m *Manager) ReloadPlugin(ctx context.Context, metadata MetadataWithDirectory) error {
logger.Info(ctx, fmt.Sprintf("start reloading plugin: %s", metadata.Metadata.Name))

pluginHost, exist := lo.Find(AllHosts, func(item Host) bool {
return strings.ToLower(string(item.GetRuntime(ctx))) == strings.ToLower(metadata.Metadata.Runtime)
})
if !exist {
return fmt.Errorf("unsupported runtime: %s", metadata.Metadata.Runtime)
}

pluginInstance, pluginInstanceExist := lo.Find(m.instances, func(item *Instance) bool {
return item.Metadata.Id == metadata.Metadata.Id
})
if pluginInstanceExist {
logger.Info(ctx, fmt.Sprintf("plugin(%s) is loaded, unload first", metadata.Metadata.Name))
m.UnloadPlugin(ctx, pluginInstance)
} else {
logger.Info(ctx, fmt.Sprintf("plugin(%s) is not loaded, skip unload", metadata.Metadata.Name))
}

loadErr := m.loadHostPlugin(ctx, pluginHost, metadata)
if loadErr != nil {
return loadErr
}

return nil
}

func (m *Manager) loadHostPlugin(ctx context.Context, host Host, metadata MetadataWithDirectory) error {
loadStartTimestamp := util.GetSystemTimestamp()
plugin, loadErr := host.LoadPlugin(ctx, metadata.Metadata, metadata.Directory)
Expand Down
2 changes: 1 addition & 1 deletion Wox/plugin/system/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (a *ApplicationPlugin) watchAppChanges(ctx context.Context) {
var appExtensions = a.retriever.GetAppExtensions(ctx)
for _, d := range appDirectories {
var directory = d
util.WatchDirectories(ctx, directory.Path, func(e fsnotify.Event) {
util.WatchDirectoryChanges(ctx, directory.Path, func(e fsnotify.Event) {
var appPath = e.Name
var isExtensionMatch = lo.ContainsBy(appExtensions, func(ext string) bool {
return strings.HasSuffix(e.Name, fmt.Sprintf(".%s", ext))
Expand Down
Loading

0 comments on commit f67d189

Please sign in to comment.