fix: plugin not unloaded when reloading

Massimo Melina committed Dec 15, 2022 at 14:15 UTC 64d2b6135990f63de94bf8848e261501dfa8e81e
2 files changed +27 -23
src/api.plugins.ts
+3 -2
@@ -29,8 +29,9 @@ const apis: ApiHandlers = {
29 })
30
31 function serialize(p: Readonly<Plugin> | AvailablePlugin) {
32 - return _.defaults('getData' in p ? Object.assign(_.pick(p, ['id','started']), p.getData()) : p,
33 - { started: null, badApi: null }) // nulls should be used to be sure to overwrite previous values,
32 + const o = 'getData' in p ? Object.assign(_.pick(p, ['id','started']), p.getData())
33 + : { ...p } // _.defaults mutates object, and we don't want that
34 + return _.defaults(o, { started: null, badApi: null }) // nulls should be used to be sure to overwrite previous values,
35 }
36 },
37
src/plugins.ts
+24 -21
@@ -3,7 +3,7 @@
3 import glob from 'fast-glob'
4 import { watchLoad } from './watchLoad'
5 import _ from 'lodash'
6 -import pathLib, { join } from 'path'
6 +import pathLib from 'path'
7 import { API_VERSION, APP_PATH, COMPATIBLE_API_VERSION, PLUGINS_PUB_URI } from './const'
8 import * as Const from './const'
9 import Koa from 'koa'
@@ -57,7 +57,8 @@ export function setPluginConfig(id: string, changes: Dict) {
57 }
58
59 export function getPluginInfo(id: string) {
60 - return plugins[id]?.getData() ?? availablePlugins[id]
60 + const running = plugins[id]?.getData()
61 + return running && Object.assign(running, {id}) || availablePlugins[id]
62 }
63
64 export function mapPlugins<T>(cb:(plugin:Readonly<Plugin>, pluginName:string)=> T) {
@@ -116,16 +117,8 @@ export class Plugin {
117
118 constructor(readonly id:string, private readonly data:any, private unwatch:()=>void){
119 if (!data) throw 'invalid data'
119 - // if a previous instance is present, we are going to overwrite it, but first call its unload callback
120 - const old = plugins[id]
121 - try { old?.data?.unload?.() } // we don't want all the effects of the Plugin.unload
122 - catch(e){
123 - console.debug('error unloading plugin', id, String(e))
124 - }
125 - // track this
126 - const wasStopped = availablePlugins[id]
127 - if (wasStopped)
128 - delete availablePlugins[id]
120 + if (plugins[id])
121 + throw "unload first: " + id
122 plugins[id] = this
123
124 this.data = data = { ...data } // clone to make object modifiable. Objects coming from import are not.
@@ -139,7 +132,6 @@ export class Plugin {
132 console.warn('invalid', k)
133 }
134 }
142 - events.emit(old || wasStopped ? 'pluginStarted' : 'pluginInstalled', this)
135 }
136 get middleware(): undefined | PluginMiddleware {
137 return this.data?.middleware
@@ -158,14 +150,17 @@ export class Plugin {
150 return { ...this.data }
151 }
152
161 - async unload() {
153 + async unload(reloading=false) {
154 const { id } = this
163 - console.log('unloading plugin', id)
164 - try { await this.data?.unload?.() }
155 + try {
156 + await this.data?.unload?.()
157 + console.log('unloaded plugin', id)
158 + }
159 catch(e) {
166 - console.debug('error unloading plugin', id, String(e))
160 + console.log('error unloading plugin', id, String(e))
161 }
162 delete plugins[id]
163 + if (reloading) return
164 this.unwatch()
165 if (availablePlugins[id])
166 events.emit('pluginStopped', availablePlugins[id])
@@ -227,8 +222,8 @@ export async function rescan() {
222 const module = pathLib.resolve(f)
223 const { unwatch } = watchLoad(f, async () => {
224 try {
230 - const reloading = plugins[id]
231 - console.log(reloading ? "reloading plugin" : "loading plugin", id)
225 + const alreadyRunning = plugins[id]
226 + console.log(alreadyRunning ? "reloading plugin" : "loading plugin", id)
227 const { init, ...data } = await import(module)
228 delete data.default
229 deleteModule(require.resolve(module)) // avoid caching at next import
@@ -236,6 +231,7 @@ export async function rescan() {
231 if (data.badApi)
232 console.log("plugin", id, data.badApi)
233
234 + await alreadyRunning?.unload(true)
235 const res = await init?.call(null, {
236 srcDir: __dirname,
237 const: Const,
@@ -261,9 +257,16 @@ export async function rescan() {
257 getHfsConfig: getConfig,
258 })
259 Object.assign(data, res)
264 - new Plugin(id, data, unwatch)
265 - if (reloading)
260 + const plugin = new Plugin(id, data, unwatch)
261 + if (alreadyRunning)
262 events.emit('pluginUpdated', getPluginInfo(id))
263 + else {
264 + const wasInstalled = availablePlugins[id]
265 + if (wasInstalled)
266 + delete availablePlugins[id]
267 + events.emit(wasInstalled ? 'pluginStarted' : 'pluginInstalled', plugin)
268 + }
269 +
270 } catch (e) {
271 console.log("plugin error:", e)
272 }