From 8eb10b8a1c389728ae2aada014bf049fba1c36ee Mon Sep 17 00:00:00 2001 From: Anders Evenrud Date: Fri, 10 Apr 2020 23:52:20 +0200 Subject: [PATCH] Implemented package installation from URL (#28) This also adds in manifest generation --- package.json | 2 ++ src/packages.js | 44 ++++++++++++++++++++++++++++++++++++--- src/providers/packages.js | 7 +++++-- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5a62180..329c18e 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "homepage": "https://github.com/os-js/osjs-server#readme", "dependencies": { "@osjs/common": "^3.0.8", + "bent": "^7.1.2", "body-parser": "^1.19.0", "chokidar": "^3.3.1", "connect-loki": "^1.1.0", @@ -53,6 +54,7 @@ "morgan": "^1.9.1", "nocache": "^2.1.0", "sanitize-filename": "^1.6.3", + "tar": "^6.0.1", "uuid": "^3.4.0" }, "devDependencies": { diff --git a/src/packages.js b/src/packages.js index 5cdf1e8..057ec25 100644 --- a/src/packages.js +++ b/src/packages.js @@ -32,6 +32,8 @@ const fs = require('fs-extra'); const fg = require('fast-glob'); const path = require('path'); const consola = require('consola'); +const bent = require('bent'); +const tar = require('tar'); const Package = require('./package.js'); const {getPrefix} = require('./utils/vfs.js'); const logger = consola.withTag('Packages'); @@ -42,10 +44,17 @@ const readOrDefault = filename => fs.existsSync(filename) ? fs.readJsonSync(filename) : []; +const extract = (stream, target) => new Promise((resolve, reject) => { + stream.once('end', () => resolve()); + stream.once('error', error => reject(error)); + stream.pipe(tar.extract({C: target})); +}); + /** * @typedef InstallPackageOptions + * @param {string} root * @param {boolean} system - * @param {object} [auth] + * @param {object} [headers] */ /** @@ -144,9 +153,38 @@ class Packages { * Installs a package from given url * @param {string} url * @param {InstallPackageOptions} options + * @param {object} user */ - async installPackage(url, options) { - throw new Error('Not implemented yet'); + async installPackage(url, options, user) { + const {realpath} = this.core.make('osjs/vfs'); + + const name = path.basename(url.split('?')[0]) + .replace(/\.[^/.]+$/, ''); + + const stream = await bent()(url, null, { + headers: options.headers || {} + }); + + const userRoot = options.root || 'home:/.packages'; // FIXME: Client-side + const target = await realpath(`${userRoot}/${name}`, user); + const root = await realpath(userRoot, user); + const manifest = await realpath(`${userRoot}/metadata.json`, user); + + if (await fs.exists(target)) { + throw new Error('Target already exists'); + } + + if (options.system) { + throw new Error('System packages not yet implemented'); + } + + await fs.mkdir(target); + await extract(stream, target); + + const filenames = await fg(root + '/*/metadata.json'); + const metadatas = await Promise.all(filenames.map(f => fs.readJson(f))); + + await fs.writeJson(manifest, metadatas); } /** diff --git a/src/providers/packages.js b/src/providers/packages.js index 71e7457..31afff3 100644 --- a/src/providers/packages.js +++ b/src/providers/packages.js @@ -79,9 +79,12 @@ class PackageServiceProvider extends ServiceProvider { }); routeAuthenticated('POST', '/api/packages/install', (req, res) => { - this.packages.installPackage(req.body.url, req.body.options) + this.packages.installPackage(req.body.url, req.body.options, req.session.user) .then(() => res.json({success: true})) - .catch(error => res.status(400).json({error})); + .catch((error) => { + console.error(error); + res.status(400).json({error: 'Package installation failed'}); + }); }); return this.packages.init();