for reals bitch

This commit is contained in:
Daniel Huebleitner
2019-08-17 19:50:50 +02:00
parent 328888dd9e
commit e71555af1a
6 changed files with 0 additions and 440 deletions
-98
View File
@@ -1,98 +0,0 @@
import { default as PluginManager } from "./PluginManager";
import { Plugin, socketioRPC } from "frontblock-generic/Plugin";
import * as Logger from 'log4js';
Logger.configure({
appenders:
{
"PluginManager/Plugin": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories:
{
default: { appenders: [ 'PluginManager/Plugin' ], level: 'debug' }
}
})
const logger = Logger.getLogger("PluginManager/Plugin")
export default class PluginManagerPlugin extends PluginManager implements Plugin{
constructor(){
super()
this.loadedPlugins[this.name] = {backend: this, frontend: this.loadFrontend(this.name)}
}
exportExtraRPCs(): socketioRPC[] {
return [
{
name: "getAvailablePluginNames",
visibility: "private",
rpc: async() => { return await this.getAvailablePluginNames() },
type: 'call'
},
{
name: "getInstalledPluginNames",
visibility: "private",
rpc: async() => { return await this.getInstalledPluginNames() },
type: 'call'
},
{
name: "getLoadedPluginNames",
visibility: "private",
rpc: async () => { return this.getLoadedPluginNames() },
type: 'call'
},{
name: "installPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.installPlugin(pluginName) },
type: 'call'
},
{
name: "uninstallPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.uninstallPlugin(pluginName) },
type: 'call'
},
{
name: "loadPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return await this.loadPlugin(pluginName) },
type: 'call'
},
{
name: "unloadPlugin",
visibility: "private",
rpc: async(pluginName:string) => { return this.unloadPlugin(pluginName) },
type: 'call'
},{
name: "extractPlugin",
visibility: "private",
rpc: async(conf) => { return this.extractPlugin(conf) },
type: 'call'
},{
name: "downloadPlugin",
visibility: "private",
rpc: async(conf) => { return this.downloadPlugin(conf) },
type: 'call'
},{
name: "getConfig",
visibility: "private",
rpc: async() => { return this.conf },
type: 'call'
}
]
}
async start(): Promise<void> {
await Promise.all(this.getInstalledPluginNames().map(name => {
if(name == this.name) return
return this.loadPlugin(name)
}));
}
stop(): void {
}
}
-229
View File
@@ -1,229 +0,0 @@
'use strict'
import * as Logger from 'log4js';
import fetch = require('node-fetch');
import fs = require('fs');
import path = require('path')
import unzip = require('unzip')
import easyunzip = require('easy-unzip')
import { Plugin, ConfigLoader, PluginConfigLoader } from 'frontblock-generic/Plugin';
import { SemVer, parse } from "semver";
import { once } from 'events';
Logger.configure({
appenders:
{
"PluginManager": { type: 'stdout' },
//app: { type: 'file', filename: 'application.log' }
},
categories:
{
default: { appenders: [ 'PluginManager' ], level: 'debug' }
}
})
const logger = Logger.getLogger("PluginManager")
const AdmZip = require('adm-zip');
type PluginVersion = {
installed?: SemVer,
cached?: SemVer,
}
export type PluginVersioning = {
[pluginName in string]?: PluginVersion | string
}
export type PluginMap = {[pluginName:string]: {backend: Plugin, frontend:any}}
export type PluginManagerConfig = {
resourceLocation: string,
installDir:string
}
export default abstract class PluginManager extends PluginConfigLoader<PluginManagerConfig&PluginVersioning>{
private cacheDir: string
protected loadedPlugins: PluginMap = {}
constructor(){
super("PluginManager")
this.cacheDir = path.join(this.conf.installDir, '.cache')
this.initialize()
}
getDefaultConfig(): PluginManagerConfig&PluginVersioning{
return {resourceLocation: "https://gitea.frontblock.me/api/v1/repos/fb-vendor/", installDir: "plugins"}
}
private initialize() {
logger.info('Initializing manager')
try {
fs.accessSync(this.cacheDir)
logger.info('Manager already installed at ' + this.conf.installDir)
} catch (error) {
fs.mkdirSync(this.cacheDir, {recursive: true})
logger.info('Manager installed to ' + this.cacheDir)
}
}
public async getAvailablePluginNames(): Promise<string[]> {
//TODO query api
return [
'apiclient',
'paymentmanager',
'wallet',
'htmlsupplier'
]
}
public getInstalledPluginNames():string[] {
try {
const installed = fs.readdirSync(this.conf.installDir)
return installed.filter( entry => entry[0] != '.')
} catch (error) {
logger.error("getInstalledPlugins", error)
throw error
}
}
public getLoadedPluginNames(): string[] {
return Object.keys(this.loadedPlugins)
}
public getLoadedPlugins(): PluginMap {
//logger.debug(this.loadedPlugins)
return this.loadedPlugins
}
private async downloadFile(url: URL, dest: string) {
try {
const file = fs.createWriteStream(dest)
file.on('error', err => {throw new Error(err)})
const response = await fetch(url)
if (!response.ok) throw new Error(response.statusText)
response.body.pipe(file)
await once(file, 'finish');
} catch (error) {
logger.error("downloadFile", error)
await fs.promises.unlink(dest)
}
}
private async getLatestRelease(pluginName:string):Promise<any>{
const response = await fetch(this.conf.resourceLocation + pluginName + '/releases')
if (!response.ok) throw new Error(pluginName+": "+response.statusText)
const releases = await response.json()
if (!releases || !releases.length) throw new Error('Empty response')
return releases[0]
}
private async getLatestVersion(pluginName:string):Promise<SemVer>{
const latest = await this.getLatestRelease(pluginName)
const version = latest.tag_name
return parse(version)!
}
async downloadPlugin(pluginName: string) {
try {
let version = await this.getLatestVersion(pluginName)
logger.info(version)
if(this.conf[pluginName] != null && version === this.conf[pluginName]!["installed"].raw){
logger.info(pluginName, version, "already installed. skipping", this.conf[pluginName]!["installed"])
return
}
const latest = await this.getLatestRelease(pluginName)
const archiveURL = latest.assets.filter(asset => asset.name === 'plugin.zip')[0].browser_download_url
const checksumURL = latest.assets.filter(asset => asset.name === 'md5sum.txt')[0].browser_download_url
if (!version || ! archiveURL || !checksumURL) throw new Error('Malformed response')
fs.mkdirSync(path.join(this.cacheDir, pluginName), {recursive: true})
await this.downloadFile(archiveURL, path.join(this.cacheDir, pluginName, 'plugin.zip'))
await this.downloadFile(checksumURL, path.join(this.cacheDir, pluginName, 'md5sum.txt'))
if(this.conf[pluginName] == null){
this.conf[pluginName] = {}
}
this.conf[pluginName]!["cached"] = version
this.setConfig(this.conf)
} catch (error) {
logger.error("downloadPlugin", error)
}
}
extractPlugin(pluginName: string){
const archivePath = path.join(this.cacheDir, pluginName, "plugin.zip")
const outputPath = path.join(this.conf.installDir,pluginName)
//fs.createReadStream(archivePath).pipe(unzip.Extract({ path: outputPath }));
var zip = new AdmZip(archivePath);
fs.mkdirSync(outputPath, {recursive: true})
zip.extractAllTo(outputPath, true);
if(this.conf[pluginName] == null){
this.conf[pluginName] = {}
}
this.conf[pluginName]!["installed"] = this.conf[pluginName]!["cached"]
this.setConfig(this.conf)
}
public async installPlugin(pluginName: string) {
try {
let version = await this.getLatestVersion(pluginName)
if(this.conf[pluginName] == null || version != this.conf[pluginName]!["installed"].raw){
await this.downloadPlugin(pluginName)
this.extractPlugin(pluginName)
}
await this.loadPlugin(pluginName)
} catch (error) {
logger.error("installPlugin", error)
}
}
public getFrontend(pluginName: string):string{
return this.loadedPlugins[pluginName].frontend.toString()
}
public async uninstallPlugin(pluginName: string) {
//TODO check for exists
logger.info("Deleting plugin", pluginName)
await fs.promises.rmdir(path.join(this.conf.installDir,pluginName))
}
public async loadPlugin(pluginName:string) {
const pth = path.join("..", "..", this.conf.installDir, pluginName)
logger.info("Loading plugin from fs", pth+"/Plugin")
const clazz = await import(pth+"/Plugin")
logger.warn(clazz)
let obj:Plugin = new clazz.default()
await obj.start()
this.loadedPlugins[pluginName] = {backend: obj, frontend: this.loadFrontend(pluginName)}
}
public loadFrontend(pluginName:string){
const pth = path.join("..", "..", this.conf.installDir, pluginName)
logger.info("Loading frontend plugin from fs", path.join(__dirname,pth,"FrontendPlugin.js"))
return fs.readFileSync(path.join(__dirname, pth,"FrontendPlugin.js")).toString()
}
public unloadPlugin(pluginName:string):void {
if(this.loadedPlugins[pluginName] == null){
logger.warn("Cannt unload plugin: Unknown:", pluginName)
return
}
logger.info("Unloading plugin ", pluginName)
this.loadedPlugins[pluginName].backend.stop()
delete this.loadedPlugins[pluginName]
}
}