Skip to content

Commit a96c983

Browse files
committed
Added package installation API support (#21)
1 parent 54e2eb8 commit a96c983

File tree

5 files changed

+197
-33
lines changed

5 files changed

+197
-33
lines changed

src/config.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,13 @@ export const defaultConfiguration = {
183183
template: null // A string. See 'window.js' for example
184184
},
185185

186+
packages: {
187+
installation: true,
188+
local: {
189+
root: 'home:/.packages'
190+
}
191+
},
192+
186193
vfs: {
187194
defaultPath: 'osjs:/',
188195
defaultAdapter: 'system',

src/core.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,11 @@ export default class Core extends CoreBase {
196196

197197
if (result) {
198198
return connect()
199+
.then(() => {
200+
const pm = this.make('osjs/packages');
201+
202+
return pm.loadPackages();
203+
})
199204
.then(() => {
200205
this.emit('osjs/core:started');
201206
done();

src/filesystem.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,8 @@ export default class Filesystem extends EventEmitter {
339339
icon: icon(m.icon),
340340
name: m.name,
341341
label: m.label,
342-
root: m.root
342+
root: m.root,
343+
adapter: m.adapter
343344
}));
344345
}
345346

src/packages.js

Lines changed: 177 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ const createUrl = (basePath, folder, metadata, filename) =>
3636
? filename
3737
: `${basePath}${folder}/${metadata.name}/${filename}`;
3838

39+
const mapPreloads = (core, metadata, preloads) => {
40+
const {url} = core.make('osjs/vfs');
41+
42+
if (metadata._vfsRoot) {
43+
// FIXME: We might wanna do this on actual metadata creation instead
44+
const promises = preloads.map(iter => {
45+
return url(`${metadata._vfsRoot}/${iter}`);
46+
});
47+
48+
return Promise.all(promises)
49+
.then(results => [].concat(...results));
50+
}
51+
52+
return Promise.resolve(preloads);
53+
};
54+
3955
/**
4056
* A registered package reference
4157
* @property {Object} metadata Package metadata
@@ -59,13 +75,6 @@ const createUrl = (basePath, folder, metadata, filename) =>
5975
* @typedef PackageMetadata
6076
*/
6177

62-
/*
63-
* Fetch package manifest
64-
*/
65-
const fetchManifest = core =>
66-
fetch(core.url('/metadata.json'))
67-
.then(response => response.json());
68-
6978
/**
7079
* Package Manager
7180
*
@@ -109,6 +118,9 @@ export default class Packages {
109118
* @type {String[]}
110119
*/
111120
this.running = [];
121+
122+
this._systemMetadata = [];
123+
this._userMetadata = [];
112124
}
113125

114126
/**
@@ -117,6 +129,8 @@ export default class Packages {
117129
destroy() {
118130
this.packages = [];
119131
this.metadata = [];
132+
this._systemMetadata = [];
133+
this._userMetadata = [];
120134
}
121135

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

134-
return fetchManifest(this.core)
135-
.then(metadata => {
136-
this.metadata = metadata.map(iter => Object.assign({type: 'application'}, iter));
137-
});
148+
return Promise.resolve(true);
149+
}
150+
151+
/**
152+
* Loads user and system packages
153+
* @return {Promise<undefined, Error>}
154+
*/
155+
loadPackages() {
156+
const err = e => console.warn(e);
157+
158+
return Promise.all([
159+
this.loadSystemPackages().catch(err),
160+
this.loadUserPackages().catch(err)
161+
]);
162+
}
163+
164+
/**
165+
* Loads system installed packages
166+
* @return {Promise<undefined, Error>}
167+
*/
168+
loadSystemPackages() {
169+
const fetchSystemManifest = () =>
170+
fetch(this.core.url('/metadata.json'))
171+
.then(response => response.json());
172+
173+
return fetchSystemManifest()
174+
.then(json => this._setPackages(json, 'system'));
175+
}
176+
177+
/**
178+
* Loads user installed packages
179+
* @return {Promise<undefined, Error>}
180+
*/
181+
loadUserPackages() {
182+
const {readfile} = this.core.make('osjs/vfs');
183+
const {root} = this.core.config('packages.local');
184+
const localManifest = `${root}/metadata.json`;
185+
186+
const parseLocal = str => typeof str === 'string' && str
187+
? JSON.parse(str)
188+
: [];
189+
190+
const fetchLocalManifest = () =>
191+
readfile({path: localManifest})
192+
.then(parseLocal);
193+
194+
const compability = json => json.map(iter => Object.assign({
195+
_vfsRoot: `${root}/${iter.name}`
196+
}, iter));
197+
198+
return fetchLocalManifest()
199+
.then(compability)
200+
.then(json => this._setPackages(json, 'user'));
201+
}
202+
203+
/**
204+
* Internal method for populating the installed packages metadata list
205+
* @param {Object[]} list List of metadatas
206+
* @param {string} scope Package scope
207+
*/
208+
_setPackages(list, scope) {
209+
this[`_${scope}Metadata`] = list;
210+
211+
const map = iter => Object.assign({type: 'application'}, iter);
212+
213+
this.metadata = [
214+
...this._systemMetadata,
215+
...this._userMetadata
216+
].map(map);
138217
}
139218

140219
/**
@@ -202,7 +281,7 @@ export default class Packages {
202281
}
203282

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

208287
if (metadata.singleton) {
@@ -246,10 +325,12 @@ export default class Packages {
246325
*
247326
* @param {String} name Package name
248327
* @param {String} type Package type
328+
* @param {Object} [options] Launch options
329+
* @param {Boolean} [options.forcePreload=false] Force preload reloading
249330
* @throws {Error}
250331
* @return {Promise<Object, Error>}
251332
*/
252-
_launchTheme(name, type) {
333+
_launchTheme(name, type, options = {}) {
253334
const _ = this.core.make('osjs/locale').translate;
254335
const folder = type === 'icons' ? 'icons' : 'themes';
255336
const basePath = this.core.config('public');
@@ -265,13 +346,16 @@ export default class Packages {
265346
const preloads = (metadata.files || [])
266347
.map(f => this.core.url(createUrl(basePath, folder, metadata, f)));
267348

268-
return this.preload(preloads)
269-
.then(result => {
270-
return Object.assign(
271-
{elements: {}},
272-
result,
273-
this.packages.find(pkg => pkg.metadata.name === name) || {}
274-
);
349+
return mapPreloads(this.core, metadata, preloads)
350+
.then(preloads => {
351+
return this.preload(preloads, options.forcePreload === true)
352+
.then(result => {
353+
return Object.assign(
354+
{elements: {}},
355+
result,
356+
this.packages.find(pkg => pkg.metadata.name === name) || {}
357+
);
358+
});
275359
});
276360
}
277361

@@ -340,18 +424,21 @@ export default class Packages {
340424
return app;
341425
};
342426

343-
return this.preload(preloads, options.forcePreload === true)
344-
.then(({errors}) => {
345-
if (errors.length) {
346-
fail(_('ERR_PACKAGE_LOAD', name, errors.join(', ')));
347-
}
427+
return mapPreloads(this.core, metadata, preloads)
428+
.then(preloads => {
429+
return this.preload(preloads, options.forcePreload === true)
430+
.then(({errors}) => {
431+
if (errors.length) {
432+
fail(_('ERR_PACKAGE_LOAD', name, errors.join(', ')));
433+
}
348434

349-
const found = this.packages.find(pkg => pkg.metadata.name === name);
350-
if (!found) {
351-
fail(_('ERR_PACKAGE_NO_RUNTIME', name));
352-
}
435+
const found = this.packages.find(pkg => pkg.metadata.name === name);
436+
if (!found) {
437+
fail(_('ERR_PACKAGE_NO_RUNTIME', name));
438+
}
353439

354-
return create(found);
440+
return create(found);
441+
});
355442
});
356443
}
357444

@@ -382,6 +469,66 @@ export default class Packages {
382469
});
383470
}
384471

472+
/**
473+
* Installs a package
474+
* @param {Object} options
475+
* @param {File|Blob|ArrayBuffer} [options.file]
476+
* @param {boolean} [options.local=true]
477+
*/
478+
install(options) {
479+
// TODO: Progress Dialog
480+
// TODO: Locales
481+
// TODO: Better error handling
482+
483+
options = Object.assign({
484+
file: null,
485+
local: true
486+
}, options);
487+
488+
const {mountpoints} = this.core.make('osjs/fs');
489+
490+
const checkSupported = (path) => {
491+
const [name] = path.split(':');
492+
const found = mountpoints()
493+
.find(mount => mount.name === name);
494+
495+
// FIXME: Should check for 'local' instead probably
496+
return found && found.adapter === 'system';
497+
};
498+
499+
const enabled = this.core.config('packages.installation');
500+
if (!enabled) {
501+
return Promise.reject(new Error('Package installation not enabled.'));
502+
}
503+
504+
if (options.local) {
505+
const {root} = this.core.config('packages.local');
506+
if (!checkSupported(root)) {
507+
return Promise.reject(new Error('Cannot install local packages on a non-system VFS adapter'));
508+
}
509+
}
510+
511+
const reloader = options.local
512+
? () => this.loadUserPackages()
513+
: () => this.loadSystemPackages();
514+
515+
return this.core
516+
.request('/packages/install', {
517+
method: 'post',
518+
body: options
519+
}, 'json')
520+
.then(result => {
521+
if (result.success) {
522+
return reloader()
523+
.catch(err => console.warn(err));
524+
}
525+
526+
console.error(result.errors);
527+
528+
return Promise.reject(new Error('Package installaction was unsuccessful'));
529+
});
530+
}
531+
385532
/**
386533
* Gets a list of packages (metadata)
387534
* @param {Function} [filter] A filter function

src/providers/core.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,11 @@ export default class CoreServiceProvider extends ServiceProvider {
266266
register: (...args) => this.pm.register(...args),
267267
launch: (...args) => this.pm.launch(...args),
268268
preload: (...args) => this.pm.preload(...args),
269-
running: () => this.pm.running
269+
running: () => this.pm.running,
270+
loadSystemPackages: () => this.pm.loadSystemPackages(),
271+
loadUserPackages: () => this.pm.loadUserPackages(),
272+
loadPackages: () => this.pm.loadPackages(),
273+
install: (options) => this.pm.install(options)
270274
}));
271275

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

328332
this.core.on('osjs/packages:metadata:changed', () => {
329-
this.pm.init();
333+
this.pm.loadSystemPackages();
330334
});
331335

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

0 commit comments

Comments
 (0)