From 79493979588166ffdb88c606ed71873cdd7d4ea6 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Fri, 15 Jun 2012 23:20:36 -0400 Subject: [PATCH] shellDBus: Add a real error reporting system to InstallExtensionRemote Instead of using the 'extension-state-changed' signal to relay errors, use DBus's native error mechanism to inform the method caller that the call has failed. This requires making the method actually asynchronous so that we don't block the browser, which is stuck waiting for a reply from the browser plugin. To ensure this, we need to modify the browser plugin API to ensure its extesion installation method is asynchronous. Additionally, this lets us remove the awful, broken hacks that we used when a user clicked the "Cancel" button, replacing it by a DBus return value. https://bugzilla.gnome.org/show_bug.cgi?id=679099 --- browser-plugin/browser-plugin.c | 68 +++++++++++++++++++++++++++++++-- js/ui/extensionDownloader.js | 54 ++++++++++++-------------- js/ui/shellDBus.js | 5 ++- 3 files changed, 92 insertions(+), 35 deletions(-) diff --git a/browser-plugin/browser-plugin.c b/browser-plugin/browser-plugin.c index 6daea9963..b47b41c0e 100644 --- a/browser-plugin/browser-plugin.c +++ b/browser-plugin/browser-plugin.c @@ -41,7 +41,7 @@ "It can be used only by extensions.gnome.org" #define PLUGIN_MIME_STRING "application/x-gnome-shell-integration::Gnome Shell Integration Dummy Content-Type"; -#define PLUGIN_API_VERSION 4 +#define PLUGIN_API_VERSION 5 typedef struct { GDBusProxy *proxy; @@ -489,6 +489,12 @@ parse_args (const gchar *format_str, *(gboolean *) arg_location = NPVARIANT_TO_BOOLEAN (arg); break; + + case 'o': + if (!NPVARIANT_IS_OBJECT (arg)) + goto out; + + *(NPObject **) arg_location = NPVARIANT_TO_OBJECT (arg); } } @@ -580,6 +586,53 @@ plugin_enable_extension (PluginObject *obj, return ret; } +typedef struct _AsyncClosure AsyncClosure; + +struct _AsyncClosure { + PluginObject *obj; + NPObject *callback; + NPObject *errback; +}; + +static void +install_extension_cb (GObject *proxy, + GAsyncResult *async_res, + gpointer user_data) +{ + AsyncClosure *async_closure = (AsyncClosure *) user_data; + GError *error = NULL; + GVariant *res; + NPVariant args[1]; + NPVariant result = { NPVariantType_Void }; + NPObject *callback; + + res = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), async_res, &error); + + if (res == NULL) + { + if (g_dbus_error_is_remote_error (error)) + g_dbus_error_strip_remote_error (error); + STRINGZ_TO_NPVARIANT (error->message, args[0]); + callback = async_closure->errback; + } + else + { + char *string_result; + g_variant_get (res, "(&s)", &string_result); + STRINGZ_TO_NPVARIANT (string_result, args[0]); + callback = async_closure->callback; + } + + funcs.invokeDefault (async_closure->obj->instance, + callback, args, 1, &result); + + funcs.releasevariantvalue (&result); + + funcs.releaseobject (async_closure->callback); + funcs.releaseobject (async_closure->errback); + g_slice_free (AsyncClosure, async_closure); +} + static gboolean plugin_install_extension (PluginObject *obj, uint32_t argc, @@ -587,18 +640,25 @@ plugin_install_extension (PluginObject *obj, NPVariant *result) { gchar *uuid; + NPObject *callback, *errback; + AsyncClosure *async_closure; - if (!parse_args ("u", argc, argv, &uuid)) + if (!parse_args ("uoo", argc, argv, &uuid, &callback, &errback)) return FALSE; + async_closure = g_slice_new (AsyncClosure); + async_closure->obj = obj; + async_closure->callback = funcs.retainobject (callback); + async_closure->errback = funcs.retainobject (errback); + g_dbus_proxy_call (obj->proxy, "InstallRemoteExtension", g_variant_new ("(s)", uuid), G_DBUS_CALL_FLAGS_NONE, -1, /* timeout */ NULL, /* cancellable */ - NULL, /* callback */ - NULL /* user_data */); + install_extension_cb, + async_closure); g_free (uuid); diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js index e9f49524c..1d4c46847 100644 --- a/js/ui/extensionDownloader.js +++ b/js/ui/extensionDownloader.js @@ -22,7 +22,7 @@ const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/'; let _httpSession; -function installExtensionFromUUID(uuid) { +function installExtensionFromUUID(uuid, invocation) { let params = { uuid: uuid, shell_version: Config.PACKAGE_VERSION }; @@ -31,6 +31,7 @@ function installExtensionFromUUID(uuid) { _httpSession.queue_message(message, function(session, message) { if (message.status_code != Soup.KnownStatusCode.OK) { ExtensionSystem.logExtensionError(uuid, 'downloading info: ' + message.status_code); + invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString()); return; } @@ -39,10 +40,11 @@ function installExtensionFromUUID(uuid) { info = JSON.parse(message.response_body.data); } catch (e) { ExtensionSystem.logExtensionError(uuid, 'parsing info: ' + e); + invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString()); return; } - let dialog = new InstallExtensionDialog(uuid, info); + let dialog = new InstallExtensionDialog(uuid, info, invocation); dialog.open(global.get_current_time()); }); } @@ -63,9 +65,9 @@ function uninstallExtensionFromUUID(uuid) { return true; } -function gotExtensionZipFile(session, message, uuid) { +function gotExtensionZipFile(session, message, uuid, callback, errback) { if (message.status_code != Soup.KnownStatusCode.OK) { - ExtensionSystem.logExtensionError(uuid, 'downloading extension: ' + message.status_code); + errback('DownloadExtensionError', message.status_code); return; } @@ -74,7 +76,7 @@ function gotExtensionZipFile(session, message, uuid) { if (!dir.query_exists(null)) dir.make_directory_with_parents(null); } catch (e) { - ExtensionSystem.logExtensionError(uuid, 'Could not create extension directory'); + errback('CreateExtensionDirectoryError', e); return; } @@ -89,7 +91,7 @@ function gotExtensionZipFile(session, message, uuid) { null); if (!success) { - ExtensionSystem.logExtensionError(uuid, 'extract: could not extract'); + errback('ExtractExtensionError'); return; } @@ -97,8 +99,7 @@ function gotExtensionZipFile(session, message, uuid) { GLib.spawn_close_pid(pid); if (status != 0) { - ExtensionSystem.logExtensionError(uuid, 'extract: could not extract'); - invocation.return_dbus_error('org.gnome.Shell.ExtractExtensionError', ''); + errback('ExtractExtensionError'); return; } @@ -111,6 +112,7 @@ function gotExtensionZipFile(session, message, uuid) { let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); ExtensionSystem.loadExtension(extension); + callback(); }); } @@ -118,11 +120,12 @@ const InstallExtensionDialog = new Lang.Class({ Name: 'InstallExtensionDialog', Extends: ModalDialog.ModalDialog, - _init: function(uuid, info) { + _init: function(uuid, info, invocation) { this.parent({ styleClass: 'extension-dialog' }); this._uuid = uuid; this._info = info; + this._invocation = invocation; this.setButtons([{ label: _("Cancel"), action: Lang.bind(this, this._onCancelButtonPressed), @@ -147,34 +150,27 @@ const InstallExtensionDialog = new Lang.Class({ _onCancelButtonPressed: function(button, event) { this.close(global.get_current_time()); - - // Even though the extension is already "uninstalled", send through - // a state-changed signal for any users who want to know if the install - // went through correctly -- using proper async DBus would block more - // traditional clients like the plugin - let meta = { uuid: this._uuid, - state: ExtensionSystem.ExtensionState.UNINSTALLED, - error: '' }; - - _signals.emit('extension-state-changed', meta); + this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled'])); }, _onInstallButtonPressed: function(button, event) { - let state = { uuid: this._uuid, - state: ExtensionSystem.ExtensionState.DOWNLOADING, - error: '' }; - - _signals.emit('extension-state-changed', state); - let params = { shell_version: Config.PACKAGE_VERSION }; let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid); let message = Soup.form_request_new_from_hash('GET', url, params); - _httpSession.queue_message(message, - Lang.bind(this, function(session, message) { - gotExtensionZipFile(session, message, this._uuid); - })); + let invocation = this._invocation; + function errback(code, message) { + invocation.return_dbus_error('org.gnome.Shell.' + code, message ? message.toString() : ''); + } + + function callback() { + invocation.return_value(GLib.Variant.new('(s)', 'successful')); + } + + _httpSession.queue_message(message, Lang.bind(this, function(session, message) { + gotExtensionZipFile(session, message, this._uuid, callback, errback); + })); this.close(global.get_current_time()); } diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index 910d5cfc8..e402cabbb 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -202,6 +202,7 @@ const GnomeShellExtensionsIface = + @@ -285,8 +286,8 @@ const GnomeShellExtensions = new Lang.Class({ return extension.errors; }, - InstallRemoteExtension: function(uuid) { - ExtensionDownloader.installExtensionFromUUID(uuid); + InstallRemoteExtensionAsync: function([uuid], invocation) { + return ExtensionDownloader.installExtensionFromUUID(uuid, invocation); }, UninstallExtension: function(uuid) {