plugins: update deletes old files (preserving storage folder)
Massimo Melina committed
Jun 2, 2024 at 11:18 UTC
f67f2e5a2675eaa2fc171c1132c91539579c60dd
2 files changed
+25
-13
src/api.plugins.ts
-4
@@ -143,11 +143,7 @@ const apis: ApiHandlers = {
143
if (!online)
144
return new ApiError(HTTP_CONFLICT)
145
await checkDependencies(online)
146
- const enabled = isPluginEnabled(id)
147
- await stopPlugin(id)
146
await downloadPlugin(found.repo, { branch: online.branch, overwrite: true })
149
- if (enabled)
150
- void startPlugin(id) // don't wait, in case it fails to start
147
return {}
148
},
149
src/github.ts
+25
-9
@@ -1,9 +1,11 @@
1
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
3
import events from './events'
4
-import { DAY, httpString, httpStream, unzip, AsapStream, debounceAsync, asyncGeneratorToArray } from './misc'
5
-import { DISABLING_SUFFIX, findPluginByRepo, getAvailablePlugins, getPluginInfo, mapPlugins,
6
- parsePluginSource, PATH as PLUGINS_PATH, Repo } from './plugins'
4
+import { DAY, httpString, httpStream, unzip, AsapStream, debounceAsync, asyncGeneratorToArray, wait } from './misc'
5
+import {
6
+ DISABLING_SUFFIX, findPluginByRepo, getAvailablePlugins, getPluginInfo, isPluginEnabled, mapPlugins,
7
+ parsePluginSource, PATH as PLUGINS_PATH, Repo, startPlugin, stopPlugin, STORAGE_FOLDER
8
+} from './plugins'
9
import { ApiError } from './apiMiddleware'
10
import _ from 'lodash'
11
import { DEV, HFS_REPO, HFS_REPO_BRANCH, HTTP_BAD_REQUEST, HTTP_CONFLICT, HTTP_FORBIDDEN, HTTP_NOT_ACCEPTABLE,
@@ -66,6 +68,7 @@ export async function downloadPlugin(repo: Repo, { branch='', overwrite=false }=
68
69
async function go(url: string, folder: string, zipRoot: string) {
70
const installPath = PLUGINS_PATH + '/' + folder
71
+ const tempInstallPath = installPath + '--' + DISABLING_SUFFIX
72
const foldersToCopy = [ // from longer to shorter, so we first test the longer
73
zipRoot + '-' + process.platform + '-' + process.arch,
74
zipRoot + '-' + process.platform,
@@ -73,19 +76,32 @@ export async function downloadPlugin(repo: Repo, { branch='', overwrite=false }=
76
].map(x => x + '/')
77
// github zip doesn't have content-length, so we cannot produce progress event
78
const stream = await httpStream(url)
76
- const MAIN = 'plugin.js'
79
await unzip(stream, async path => {
80
const folder = foldersToCopy.find(x => path.startsWith(x))
81
if (!folder || path.endsWith('/')) return false
82
let dest = path.slice(folder.length)
81
- if (dest === MAIN) // avoid being possibly loaded before the download is complete
82
- dest += DISABLING_SUFFIX
83
- dest = join(installPath, dest)
83
+ dest = join(tempInstallPath, dest)
84
return rm(dest, { force: true }).then(() => dest, () => false)
85
})
86
- const main = join(installPath, MAIN)
87
- await rename(main + DISABLING_SUFFIX, main) // we are good now, restore name
86
+ // ready to replace
87
+ const wasEnabled = isPluginEnabled(folder)
88
+ if (wasEnabled)
89
+ await stopPlugin(folder) // stop old
90
+ let retry = 3
91
+ while (retry--) { // move data, and consider late release of the resource, up to a few seconds
92
+ const res = rename(join(installPath, STORAGE_FOLDER), join(tempInstallPath, STORAGE_FOLDER))
93
+ if (await res.then(() => true, e => e.code === 'ENOENT')) break
94
+ await wait(1000)
95
+ }
96
+ // delete old
97
+ await rm(installPath, { recursive: true }).catch(e => {
98
+ if (e.code !== 'ENOENT') throw e
99
+ })
100
+ // final replace
101
+ await rename(tempInstallPath, installPath)
102
.catch(e => { throw e.code !== 'ENOENT' ? e : new ApiError(HTTP_NOT_ACCEPTABLE, "missing main file") })
103
+ if (wasEnabled)
104
+ void startPlugin(folder) // don't wait, in case it fails to start
105
return folder
106
}
107
}