hfs
Version:
HTTP File Server
197 lines (196 loc) • 9.81 kB
JavaScript
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkDependencies = checkDependencies;
const plugins_1 = require("./plugins");
const lodash_1 = __importDefault(require("lodash"));
const assert_1 = __importDefault(require("assert"));
const misc_1 = require("./misc");
const apiMiddleware_1 = require("./apiMiddleware");
const promises_1 = require("fs/promises");
const github_1 = require("./github");
const const_1 = require("./const");
const SendList_1 = require("./SendList");
const apis = {
get_plugins({}, ctx) {
const list = new SendList_1.SendListReadable({ addAtStart: [...(0, plugins_1.mapPlugins)(serialize, false), ...(0, plugins_1.getAvailablePlugins)().map(serialize)] });
return list.events(ctx, {
pluginInstalled: p => list.add(serialize(p)),
'pluginStarted pluginStopped pluginUpdated': p => {
const { id, ...rest } = serialize(p);
list.update({ id }, rest);
},
pluginUninstalled: id => list.remove({ id }),
pluginLog: id => list.update({ id }, { log: true }) // SendList is already capping frequency
});
},
async get_plugin_updates({}, ctx) {
return new SendList_1.SendListReadable({
async doAtStart(list) {
const errs = [];
list.events(ctx, {
pluginDownload({ repo, status }) {
var _a;
list.update({ id: (_a = (0, plugins_1.findPluginByRepo)(repo)) === null || _a === void 0 ? void 0 : _a.id }, { downloading: status !== null && status !== void 0 ? status : null });
},
pluginDownloaded({ id }) {
list.update({ id }, { updated: true });
}
});
await Promise.allSettled(lodash_1.default.map((0, github_1.getFolder2repo)(), async (repo, folder) => {
try {
if (!repo)
return;
const online = await (0, github_1.readOnlineCompatiblePlugin)(repo);
if (!online)
return;
const disk = (0, plugins_1.getPluginInfo)(folder);
if (!disk)
return; // plugin removed in the meantime?
if (online.version === disk.version)
return; // different, not just newer ones, in case a version was retired
list.add(Object.assign(online, {
id: disk.id, // id is installation-dependant, and online cannot know
installedVersion: disk.version,
repo: serialize(disk).repo, // show the user the current repo we are getting this update from, not a possibly-changed future one
downgrade: online.version < disk.version,
downloading: lodash_1.default.isString(online.repo) && github_1.downloading[online.repo],
}));
}
catch (err) {
if (err.message !== '404') // the plugin is declaring a wrong repo
errs.push(err.code || err.message);
}
}));
for (const x of lodash_1.default.uniq(errs))
list.error(x);
list.ready();
}
});
},
async start_plugin({ id }) {
if ((0, plugins_1.isPluginRunning)(id))
return { msg: 'already running' };
if (plugins_1.suspendPlugins.get())
return new apiMiddleware_1.ApiError(misc_1.HTTP_PRECONDITION_FAILED, 'all plugins suspended');
await (0, plugins_1.stopPlugin)(id);
return (0, plugins_1.startPlugin)(id).then(() => ({}), e => new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e.message));
},
async stop_plugin({ id }) {
if (!(0, plugins_1.isPluginRunning)(id))
return { msg: 'already stopped' };
await (0, plugins_1.stopPlugin)(id);
return {};
},
async set_plugin({ id, enabled, config }) {
(0, assert_1.default)(id, 'id');
if (config)
(0, plugins_1.setPluginConfig)(id, config);
if (enabled !== undefined)
(0, plugins_1.enablePlugin)(id, enabled);
return {};
},
async get_plugin({ id }) {
return {
enabled: plugins_1.enablePlugins.get().includes(id),
config: {
...(0, misc_1.newObj)((0, plugins_1.getPluginConfigFields)(id), v => v === null || v === void 0 ? void 0 : v.defaultValue),
...plugins_1.pluginsConfig.get()[id]
}
};
},
get_online_plugins({ text }, ctx) {
return new SendList_1.SendListReadable({
async doAtStart(list) {
const repos = [];
list.events(ctx, {
pluginInstalled: p => {
if (repos.includes(p.repo))
list.update({ id: p.repo }, { installed: true });
},
pluginUninstalled: folder => {
const repo = (0, github_1.getFolder2repo)()[folder];
if (typeof repo !== 'string')
return; // custom repo
if (repos.includes(repo))
list.update({ id: repo }, { installed: false });
},
pluginDownload({ repo, status }) {
if (repos.includes(repo))
list.update({ id: repo }, { downloading: status !== null && status !== void 0 ? status : null });
}
});
try {
const already = Object.values((0, github_1.getFolder2repo)()).map(String);
for await (const pl of await (0, github_1.searchPlugins)(text, { skipRepos: already })) {
const repo = pl.repo || pl.id; // .repo property can be more trustworthy in case github user renamed and left the previous link in 'repo'
const missing = await (0, plugins_1.getMissingDependencies)(pl);
if (missing.length)
pl.missing = missing;
list.add(pl);
repos.push(repo);
}
}
catch (err) {
list.error(err.code || err.message);
}
list.ready();
}
});
},
async download_plugin({ id, branch, stop }) {
await checkDependencies(await (0, github_1.readOnlinePlugin)(id, branch));
const folder = await (0, github_1.downloadPlugin)(id, { branch });
if (stop) // be sure this is not automatically started
await (0, plugins_1.stopPlugin)(folder);
return (await (0, misc_1.waitFor)(() => (0, plugins_1.getPluginInfo)(folder), { timeout: 5000 }))
|| new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
},
async update_plugin({ id, branch }) {
const found = (0, plugins_1.getPluginInfo)(id);
if (!found)
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
const online = await (0, github_1.readOnlineCompatiblePlugin)(found.repo); // branch returned by readOnlineCompatiblePlugin is possibly fresher, so we use that
if (!online)
return new apiMiddleware_1.ApiError(misc_1.HTTP_CONFLICT);
await checkDependencies(online);
await (0, github_1.downloadPlugin)(found.repo, { branch: online.branch, overwrite: true });
return {};
},
async uninstall_plugin({ id, deleteConfig }) {
await (0, plugins_1.stopPlugin)(id);
await (0, promises_1.rm)(plugins_1.PATH + '/' + id, { recursive: true, force: true });
if (deleteConfig)
(0, plugins_1.setPluginConfig)(id, null);
return {};
},
get_plugin_log({ id }, ctx) {
const p = (0, plugins_1.getPluginInfo)(id);
if (!p)
return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
const list = new SendList_1.SendListReadable({ addAtStart: p.log });
return list.events(ctx, {
['pluginLog:' + id]: x => list.add(x)
});
},
};
exports.default = apis;
function serialize(p) {
var _a;
let o = 'getData' in p ? Object.assign(lodash_1.default.pick(p, ['id', 'started']), p.getData())
: { ...p }; // _.defaults mutates object, and we don't want that
if (typeof o.repo === 'object') // custom repo
o.repo = o.repo.web;
o.log = 'log' in p && ((_a = p.log) === null || _a === void 0 ? void 0 : _a.length) > 0;
o.config && (o.config = lodash_1.default.isFunction(o.config) ? String(o.config)
: JSON.stringify(o.config, (_k, v) => lodash_1.default.isFunction(v) ? String(v) : v)); // allow simple functions
return lodash_1.default.defaults(o, { started: null, badApi: null }); // nulls should be used to be sure to overwrite previous values,
}
async function checkDependencies(plugin) {
const miss = await (0, plugins_1.getMissingDependencies)(plugin);
if (miss.length)
throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY, miss);
}
;