Skip to content

Commit

Permalink
Added package installation API support (#21)
Browse files Browse the repository at this point in the history
  • Loading branch information
andersevenrud committed Dec 6, 2018
1 parent 54e2eb8 commit a96c983
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 33 deletions.
7 changes: 7 additions & 0 deletions src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,13 @@ export const defaultConfiguration = {
template: null // A string. See 'window.js' for example
},

packages: {
installation: true,
local: {
root: 'home:/.packages'
}
},

vfs: {
defaultPath: 'osjs:/',
defaultAdapter: 'system',
Expand Down
5 changes: 5 additions & 0 deletions src/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,11 @@ export default class Core extends CoreBase {

if (result) {
return connect()
.then(() => {
const pm = this.make('osjs/packages');

return pm.loadPackages();
})
.then(() => {
this.emit('osjs/core:started');
done();
Expand Down
3 changes: 2 additions & 1 deletion src/filesystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,8 @@ export default class Filesystem extends EventEmitter {
icon: icon(m.icon),
name: m.name,
label: m.label,
root: m.root
root: m.root,
adapter: m.adapter
}));
}

Expand Down
207 changes: 177 additions & 30 deletions src/packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,22 @@ const createUrl = (basePath, folder, metadata, filename) =>
? filename
: `${basePath}${folder}/${metadata.name}/${filename}`;

const mapPreloads = (core, metadata, preloads) => {
const {url} = core.make('osjs/vfs');

if (metadata._vfsRoot) {
// FIXME: We might wanna do this on actual metadata creation instead
const promises = preloads.map(iter => {
return url(`${metadata._vfsRoot}/${iter}`);
});

return Promise.all(promises)
.then(results => [].concat(...results));
}

return Promise.resolve(preloads);
};

/**
* A registered package reference
* @property {Object} metadata Package metadata
Expand All @@ -59,13 +75,6 @@ const createUrl = (basePath, folder, metadata, filename) =>
* @typedef PackageMetadata
*/

/*
* Fetch package manifest
*/
const fetchManifest = core =>
fetch(core.url('/metadata.json'))
.then(response => response.json());

/**
* Package Manager
*
Expand Down Expand Up @@ -109,6 +118,9 @@ export default class Packages {
* @type {String[]}
*/
this.running = [];

this._systemMetadata = [];
this._userMetadata = [];
}

/**
Expand All @@ -117,6 +129,8 @@ export default class Packages {
destroy() {
this.packages = [];
this.metadata = [];
this._systemMetadata = [];
this._userMetadata = [];
}

/**
Expand All @@ -131,10 +145,75 @@ export default class Packages {
.forEach(pkg => this.launch(pkg.name));
});

return fetchManifest(this.core)
.then(metadata => {
this.metadata = metadata.map(iter => Object.assign({type: 'application'}, iter));
});
return Promise.resolve(true);
}

/**
* Loads user and system packages
* @return {Promise<undefined, Error>}
*/
loadPackages() {
const err = e => console.warn(e);

return Promise.all([
this.loadSystemPackages().catch(err),
this.loadUserPackages().catch(err)
]);
}

/**
* Loads system installed packages
* @return {Promise<undefined, Error>}
*/
loadSystemPackages() {
const fetchSystemManifest = () =>
fetch(this.core.url('/metadata.json'))
.then(response => response.json());

return fetchSystemManifest()
.then(json => this._setPackages(json, 'system'));
}

/**
* Loads user installed packages
* @return {Promise<undefined, Error>}
*/
loadUserPackages() {
const {readfile} = this.core.make('osjs/vfs');
const {root} = this.core.config('packages.local');
const localManifest = `${root}/metadata.json`;

const parseLocal = str => typeof str === 'string' && str
? JSON.parse(str)
: [];

const fetchLocalManifest = () =>
readfile({path: localManifest})
.then(parseLocal);

const compability = json => json.map(iter => Object.assign({
_vfsRoot: `${root}/${iter.name}`
}, iter));

return fetchLocalManifest()
.then(compability)
.then(json => this._setPackages(json, 'user'));
}

/**
* Internal method for populating the installed packages metadata list
* @param {Object[]} list List of metadatas
* @param {string} scope Package scope
*/
_setPackages(list, scope) {
this[`_${scope}Metadata`] = list;

const map = iter => Object.assign({type: 'application'}, iter);

this.metadata = [
...this._systemMetadata,
...this._userMetadata
].map(map);
}

/**
Expand Down Expand Up @@ -202,7 +281,7 @@ export default class Packages {
}

if (['theme', 'icons', 'sounds'].indexOf(metadata.type) !== -1) {
return this._launchTheme(name, metadata.type);
return this._launchTheme(name, metadata.type, options);
}

if (metadata.singleton) {
Expand Down Expand Up @@ -246,10 +325,12 @@ export default class Packages {
*
* @param {String} name Package name
* @param {String} type Package type
* @param {Object} [options] Launch options
* @param {Boolean} [options.forcePreload=false] Force preload reloading
* @throws {Error}
* @return {Promise<Object, Error>}
*/
_launchTheme(name, type) {
_launchTheme(name, type, options = {}) {
const _ = this.core.make('osjs/locale').translate;
const folder = type === 'icons' ? 'icons' : 'themes';
const basePath = this.core.config('public');
Expand All @@ -265,13 +346,16 @@ export default class Packages {
const preloads = (metadata.files || [])
.map(f => this.core.url(createUrl(basePath, folder, metadata, f)));

return this.preload(preloads)
.then(result => {
return Object.assign(
{elements: {}},
result,
this.packages.find(pkg => pkg.metadata.name === name) || {}
);
return mapPreloads(this.core, metadata, preloads)
.then(preloads => {
return this.preload(preloads, options.forcePreload === true)
.then(result => {
return Object.assign(
{elements: {}},
result,
this.packages.find(pkg => pkg.metadata.name === name) || {}
);
});
});
}

Expand Down Expand Up @@ -340,18 +424,21 @@ export default class Packages {
return app;
};

return this.preload(preloads, options.forcePreload === true)
.then(({errors}) => {
if (errors.length) {
fail(_('ERR_PACKAGE_LOAD', name, errors.join(', ')));
}
return mapPreloads(this.core, metadata, preloads)
.then(preloads => {
return this.preload(preloads, options.forcePreload === true)
.then(({errors}) => {
if (errors.length) {
fail(_('ERR_PACKAGE_LOAD', name, errors.join(', ')));
}

const found = this.packages.find(pkg => pkg.metadata.name === name);
if (!found) {
fail(_('ERR_PACKAGE_NO_RUNTIME', name));
}
const found = this.packages.find(pkg => pkg.metadata.name === name);
if (!found) {
fail(_('ERR_PACKAGE_NO_RUNTIME', name));
}

return create(found);
return create(found);
});
});
}

Expand Down Expand Up @@ -382,6 +469,66 @@ export default class Packages {
});
}

/**
* Installs a package
* @param {Object} options
* @param {File|Blob|ArrayBuffer} [options.file]
* @param {boolean} [options.local=true]
*/
install(options) {
// TODO: Progress Dialog
// TODO: Locales
// TODO: Better error handling

options = Object.assign({
file: null,
local: true
}, options);

const {mountpoints} = this.core.make('osjs/fs');

const checkSupported = (path) => {
const [name] = path.split(':');
const found = mountpoints()
.find(mount => mount.name === name);

// FIXME: Should check for 'local' instead probably
return found && found.adapter === 'system';
};

const enabled = this.core.config('packages.installation');
if (!enabled) {
return Promise.reject(new Error('Package installation not enabled.'));
}

if (options.local) {
const {root} = this.core.config('packages.local');
if (!checkSupported(root)) {
return Promise.reject(new Error('Cannot install local packages on a non-system VFS adapter'));
}
}

const reloader = options.local
? () => this.loadUserPackages()
: () => this.loadSystemPackages();

return this.core
.request('/packages/install', {
method: 'post',
body: options
}, 'json')
.then(result => {
if (result.success) {
return reloader()
.catch(err => console.warn(err));
}

console.error(result.errors);

return Promise.reject(new Error('Package installaction was unsuccessful'));
});
}

/**
* Gets a list of packages (metadata)
* @param {Function} [filter] A filter function
Expand Down
8 changes: 6 additions & 2 deletions src/providers/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,11 @@ export default class CoreServiceProvider extends ServiceProvider {
register: (...args) => this.pm.register(...args),
launch: (...args) => this.pm.launch(...args),
preload: (...args) => this.pm.preload(...args),
running: () => this.pm.running
running: () => this.pm.running,
loadSystemPackages: () => this.pm.loadSystemPackages(),
loadUserPackages: () => this.pm.loadUserPackages(),
loadPackages: () => this.pm.loadPackages(),
install: (options) => this.pm.install(options)
}));

this.core.instance('osjs/clipboard', () => ({
Expand Down Expand Up @@ -326,7 +330,7 @@ export default class CoreServiceProvider extends ServiceProvider {
});

this.core.on('osjs/packages:metadata:changed', () => {
this.pm.init();
this.pm.loadSystemPackages();
});

this.core.on('osjs/packages:package:changed', name => {
Expand Down

0 comments on commit a96c983

Please sign in to comment.