updating plugins

This commit is contained in:
antelle 2017-04-08 23:35:26 +02:00
parent 782488f8fa
commit c084746b3e
6 changed files with 140 additions and 26 deletions

View File

@ -455,6 +455,8 @@
"setPlInstallBtn": "Install",
"setPlInstallBtnProgress": "Installing",
"setPlUninstallBtn": "Uninstall",
"setPlUpdateBtn": "Update",
"setPlUpdating": "Updating",
"setPlLocaleBtn": "Switch to this language",
"setPlThemeBtn": "Switch to this theme",
"setPlJs": "code",
@ -463,6 +465,7 @@
"setPlCreatedBy": "created by {}",
"setPlLoadTime": "took {} to load",
"setPlLoadError": "error loading plugin",
"setPlUpdateError": "There was an error during update",
"setAboutTitle": "About",
"setAboutBuilt": "This app is built with these awesome tools",

View File

@ -9,6 +9,7 @@ const PluginManager = Backbone.Model.extend({
state: '',
installing: null,
uninstalling: null,
updating: null,
lastInstall: null,
plugins: new PluginCollection()
},
@ -33,15 +34,15 @@ const PluginManager = Backbone.Model.extend({
install(url) {
const lastInstall = { url, dt: new Date() };
this.set({ installing: url, lastInstall: lastInstall });
return Plugin.loadFromUrl(url).then(plugin =>
this.uninstall(plugin.id).then(() => {
return Plugin.loadFromUrl(url).then(plugin => {
return this.uninstall(plugin.id).then(() => {
return plugin.install().then(() => {
this.get('plugins').push(plugin);
this.set({ installing: null });
this.saveState();
});
})
).catch(e => {
});
}).catch(e => {
this.set({ installing: null, lastInstall: _.extend(lastInstall, { error: e.toString() }) });
throw e;
});
@ -61,8 +62,34 @@ const PluginManager = Backbone.Model.extend({
});
},
update(id) {
const plugins = this.get('plugins');
const oldPlugin = plugins.get(id);
if (!oldPlugin) {
return Promise.reject();
}
const url = oldPlugin.get('url');
this.set({ updating: id });
return Plugin.loadFromUrl(url).then(newPlugin => {
return oldPlugin.update(newPlugin).then(() => {
this.set({ updating: null });
this.saveState();
}).catch(e => {
this.set('updating', null);
throw e;
});
}).catch(e => {
this.set({ updating: null });
throw e;
});
},
loadPlugin(desc) {
const plugin = new Plugin(desc.manifest, desc.url, true);
const plugin = new Plugin({
manifest: desc.manifest,
url: desc.url,
local: true
});
return plugin.install()
.then(() => plugin)
.catch(() => plugin);

View File

@ -20,6 +20,7 @@ const Plugin = Backbone.Model.extend({
STATUS_INACTIVE: 'inactive',
STATUS_INSTALLING: 'installing',
STATUS_UNINSTALLING: 'uninstalling',
STATUS_UPDATING: 'updating',
STATUS_INVALID: 'invalid',
STATUS_ERROR: 'error',
@ -29,19 +30,16 @@ const Plugin = Backbone.Model.extend({
url: '',
status: 'inactive',
installTime: null,
installError: null
installError: null,
updateError: null
},
resources: null,
module: null,
initialize(manifest, url, local) {
const name = manifest.name;
this.set({
name,
manifest,
url,
local
});
initialize(options) {
const name = options.manifest.name;
this.set({ name });
this.logger = new Logger(`plugin:${name}`);
},
@ -62,7 +60,8 @@ const Plugin = Backbone.Model.extend({
this.set({
status: this.STATUS_ERROR,
installError: err,
installTime: this.logger.ts() - ts
installTime: this.logger.ts() - ts,
updateError: null
});
throw err;
});
@ -101,6 +100,16 @@ const Plugin = Backbone.Model.extend({
}
},
validateUpdatedManifest(newManifest) {
const manifest = this.get('manifest');
if (manifest.name !== newManifest.name) {
return 'Plugin name mismatch';
}
if (manifest.publicKey !== newManifest.publicKey) {
return 'Public key mismatch';
}
},
installWithManifest() {
const manifest = this.get('manifest');
const local = this.get('local');
@ -187,7 +196,7 @@ const Plugin = Backbone.Model.extend({
},
installWithResources() {
this.logger.info('Installing loaded plugin');
this.logger.info('Installing plugin code');
const manifest = this.get('manifest');
const promises = [];
if (this.resources.css) {
@ -337,6 +346,14 @@ const Plugin = Backbone.Model.extend({
delete BaseLocale[this.getThemeLocaleKey(theme.name)];
},
uninstallPluginCode() {
try {
this.module.exports.uninstall();
} catch (e) {
this.logger.error('Plugin uninstall method returned an error', e);
}
},
uninstall() {
const manifest = this.get('manifest');
this.logger.info('Uninstalling plugin with resources', Object.keys(manifest.resources).join(', '));
@ -347,11 +364,7 @@ const Plugin = Backbone.Model.extend({
this.removeElement('plugin-css-' + this.get('name'));
}
if (manifest.resources.js) {
try {
this.module.exports.uninstall();
} catch (e) {
this.logger.error('Plugin uninstall method returned an error', e);
}
this.uninstallPluginCode();
this.removeElement('plugin-js-' + this.get('name'));
}
if (manifest.resources.loc) {
@ -365,6 +378,54 @@ const Plugin = Backbone.Model.extend({
this.logger.info('Uninstall complete', this.logger.ts(ts));
});
});
},
update(newPlugin) {
const ts = this.logger.ts();
const prevStatus = this.get('status');
this.set('status', this.STATUS_UPDATING);
return Promise.resolve().then(() => {
const manifest = this.get('manifest');
const newManifest = newPlugin.get('manifest');
if (manifest.version === newManifest.version) {
this.set({ status: prevStatus, updateError: null });
this.logger.info(`v${manifest.version} is the latest plugin version`);
return;
}
this.logger.info(`Updating plugin from v${manifest.version} to v${newManifest.version}`);
const error = newPlugin.validateManifest() || this.validateUpdatedManifest(newManifest);
if (error) {
this.logger.error('Manifest validation error', error);
this.set({ status: prevStatus, updateError: error });
throw 'Plugin validation error: ' + error;
}
this.uninstallPluginCode();
return newPlugin.installWithManifest()
.then(() => {
this.module = newPlugin.module;
this.resources = newPlugin.resources;
this.set({
status: this.STATUS_ACTIVE,
manifest: newManifest,
installTime: this.logger.ts() - ts,
installError: null,
updateError: null
});
this.logger.info('Update complete', this.logger.ts(ts));
})
.catch(err => {
this.logger.error('Error updating plugin', err);
if (prevStatus === this.STATUS_ACTIVE) {
this.logger.info('Activating previous version');
this.installWithResources();
}
this.set({
status: prevStatus,
updateError: err
});
throw err;
});
});
}
});
@ -387,7 +448,9 @@ Plugin.loadFromUrl = function(url) {
throw 'Failed to parse manifest';
}
commonLogger.debug('Loaded manifest', manifest);
return new Plugin(manifest, url);
return new Plugin({
manifest, url
});
});
};

View File

@ -9,12 +9,13 @@ const SettingsPluginsView = Backbone.View.extend({
events: {
'click .settings_plugins-install-btn': 'installClick',
'click .settings_plugins-uninstall-btn': 'uninstallClick',
'click .settings_plugins-update-btn': 'updateClick',
'click .settings_plugins-use-locale-btn': 'useLocaleClick',
'click .settings_plugins-use-theme-btn': 'useThemeClick'
},
initialize() {
this.listenTo(PluginManager, 'change:installing change:uninstalling', this.render.bind(this));
this.listenTo(PluginManager, 'change:installing change:uninstalling change:updating', this.render.bind(this));
},
render() {
@ -24,10 +25,12 @@ const SettingsPluginsView = Backbone.View.extend({
id: plugin.id,
manifest: plugin.get('manifest'),
status: plugin.get('status'),
installTime: Math.round(plugin.get('installTime'))
installTime: Math.round(plugin.get('installTime')),
updateError: plugin.get('updateError')
})),
lastInstallUrl: PluginManager.get('installing') || (lastInstall.error ? lastInstall.url : ''),
lastInstallError: lastInstall.error
lastInstallError: lastInstall.error,
updating: PluginManager.get('updating')
});
return this;
},
@ -63,9 +66,20 @@ const SettingsPluginsView = Backbone.View.extend({
uninstallClick(e) {
const pluginId = $(e.target).data('plugin');
if (PluginManager.get('updating') === pluginId || PluginManager.get('uninstalling') === pluginId) {
return;
}
PluginManager.uninstall(pluginId);
},
updateClick(e) {
const pluginId = $(e.target).data('plugin');
if (PluginManager.get('updating') === pluginId || PluginManager.get('uninstalling') === pluginId) {
return;
}
PluginManager.update(pluginId);
},
useLocaleClick(e) {
const locale = $(e.target).data('locale');
AppSettingsModel.instance.set('locale', locale);

View File

@ -160,5 +160,8 @@
&-plugin-desc {
margin-bottom: $base-padding-v;
}
&-upd-error {
margin-top: $base-padding-v;
}
}
}

View File

@ -17,9 +17,13 @@
{{#res 'setPlCreatedBy'}}<a href="{{plugin.manifest.author.url}}" target="_blank">{{plugin.manifest.author.name}}</a> ({{plugin.manifest.author.email}}){{/res}},
{{#res 'setPlLoadTime'}}{{plugin.installTime}}ms{{/res}}
{{#ifeq plugin.status 'error'}}<span class="error-color">&nbsp;({{res 'setPlLoadError'}}){{/ifeq}}
{{#if plugin.updateError}}<div class="error-color settings__plugins-upd-error">{{res 'setPlUpdateError'}}: <pre>{{plugin.updateError}}</pre></div>{{/if}}
</div>
<div class="settings__plugins-plugin-buttons">
<button class="settings_plugins-uninstall-btn btn-silent" data-plugin="{{plugin.id}}">{{res 'setPlUninstallBtn'}}</button>
<button class="settings_plugins-uninstall-btn btn-error" data-plugin="{{plugin.id}}">{{res 'setPlUninstallBtn'}}</button>
<button class="settings_plugins-update-btn btn-silent" data-plugin="{{plugin.id}}">
{{~#ifeq plugin.id ../updating}}{{res 'setPlUpdating'}}&hellip;{{else}}{{res 'setPlUpdateBtn'}}{{/ifeq~}}
</button>
{{#if plugin.manifest.locale}}
<button class="settings_plugins-use-locale-btn btn-silent" data-locale="{{plugin.manifest.locale.name}}">{{res 'setPlLocaleBtn'}}</button>
{{/if}}