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
This commit is contained in:
Jasper St. Pierre 2012-06-15 23:20:36 -04:00
parent 1d1359b58f
commit 7949397958
3 changed files with 92 additions and 35 deletions

View File

@ -41,7 +41,7 @@
"It can be used only by extensions.gnome.org" "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_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 { typedef struct {
GDBusProxy *proxy; GDBusProxy *proxy;
@ -489,6 +489,12 @@ parse_args (const gchar *format_str,
*(gboolean *) arg_location = NPVARIANT_TO_BOOLEAN (arg); *(gboolean *) arg_location = NPVARIANT_TO_BOOLEAN (arg);
break; 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; 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 static gboolean
plugin_install_extension (PluginObject *obj, plugin_install_extension (PluginObject *obj,
uint32_t argc, uint32_t argc,
@ -587,18 +640,25 @@ plugin_install_extension (PluginObject *obj,
NPVariant *result) NPVariant *result)
{ {
gchar *uuid; 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; 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, g_dbus_proxy_call (obj->proxy,
"InstallRemoteExtension", "InstallRemoteExtension",
g_variant_new ("(s)", uuid), g_variant_new ("(s)", uuid),
G_DBUS_CALL_FLAGS_NONE, G_DBUS_CALL_FLAGS_NONE,
-1, /* timeout */ -1, /* timeout */
NULL, /* cancellable */ NULL, /* cancellable */
NULL, /* callback */ install_extension_cb,
NULL /* user_data */); async_closure);
g_free (uuid); g_free (uuid);

View File

@ -22,7 +22,7 @@ const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/';
let _httpSession; let _httpSession;
function installExtensionFromUUID(uuid) { function installExtensionFromUUID(uuid, invocation) {
let params = { uuid: uuid, let params = { uuid: uuid,
shell_version: Config.PACKAGE_VERSION }; shell_version: Config.PACKAGE_VERSION };
@ -31,6 +31,7 @@ function installExtensionFromUUID(uuid) {
_httpSession.queue_message(message, function(session, message) { _httpSession.queue_message(message, function(session, message) {
if (message.status_code != Soup.KnownStatusCode.OK) { if (message.status_code != Soup.KnownStatusCode.OK) {
ExtensionSystem.logExtensionError(uuid, 'downloading info: ' + message.status_code); ExtensionSystem.logExtensionError(uuid, 'downloading info: ' + message.status_code);
invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
return; return;
} }
@ -39,10 +40,11 @@ function installExtensionFromUUID(uuid) {
info = JSON.parse(message.response_body.data); info = JSON.parse(message.response_body.data);
} catch (e) { } catch (e) {
ExtensionSystem.logExtensionError(uuid, 'parsing info: ' + e); ExtensionSystem.logExtensionError(uuid, 'parsing info: ' + e);
invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
return; return;
} }
let dialog = new InstallExtensionDialog(uuid, info); let dialog = new InstallExtensionDialog(uuid, info, invocation);
dialog.open(global.get_current_time()); dialog.open(global.get_current_time());
}); });
} }
@ -63,9 +65,9 @@ function uninstallExtensionFromUUID(uuid) {
return true; return true;
} }
function gotExtensionZipFile(session, message, uuid) { function gotExtensionZipFile(session, message, uuid, callback, errback) {
if (message.status_code != Soup.KnownStatusCode.OK) { if (message.status_code != Soup.KnownStatusCode.OK) {
ExtensionSystem.logExtensionError(uuid, 'downloading extension: ' + message.status_code); errback('DownloadExtensionError', message.status_code);
return; return;
} }
@ -74,7 +76,7 @@ function gotExtensionZipFile(session, message, uuid) {
if (!dir.query_exists(null)) if (!dir.query_exists(null))
dir.make_directory_with_parents(null); dir.make_directory_with_parents(null);
} catch (e) { } catch (e) {
ExtensionSystem.logExtensionError(uuid, 'Could not create extension directory'); errback('CreateExtensionDirectoryError', e);
return; return;
} }
@ -89,7 +91,7 @@ function gotExtensionZipFile(session, message, uuid) {
null); null);
if (!success) { if (!success) {
ExtensionSystem.logExtensionError(uuid, 'extract: could not extract'); errback('ExtractExtensionError');
return; return;
} }
@ -97,8 +99,7 @@ function gotExtensionZipFile(session, message, uuid) {
GLib.spawn_close_pid(pid); GLib.spawn_close_pid(pid);
if (status != 0) { if (status != 0) {
ExtensionSystem.logExtensionError(uuid, 'extract: could not extract'); errback('ExtractExtensionError');
invocation.return_dbus_error('org.gnome.Shell.ExtractExtensionError', '');
return; return;
} }
@ -111,6 +112,7 @@ function gotExtensionZipFile(session, message, uuid) {
let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER); let extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
ExtensionSystem.loadExtension(extension); ExtensionSystem.loadExtension(extension);
callback();
}); });
} }
@ -118,11 +120,12 @@ const InstallExtensionDialog = new Lang.Class({
Name: 'InstallExtensionDialog', Name: 'InstallExtensionDialog',
Extends: ModalDialog.ModalDialog, Extends: ModalDialog.ModalDialog,
_init: function(uuid, info) { _init: function(uuid, info, invocation) {
this.parent({ styleClass: 'extension-dialog' }); this.parent({ styleClass: 'extension-dialog' });
this._uuid = uuid; this._uuid = uuid;
this._info = info; this._info = info;
this._invocation = invocation;
this.setButtons([{ label: _("Cancel"), this.setButtons([{ label: _("Cancel"),
action: Lang.bind(this, this._onCancelButtonPressed), action: Lang.bind(this, this._onCancelButtonPressed),
@ -147,33 +150,26 @@ const InstallExtensionDialog = new Lang.Class({
_onCancelButtonPressed: function(button, event) { _onCancelButtonPressed: function(button, event) {
this.close(global.get_current_time()); this.close(global.get_current_time());
this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
// 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);
}, },
_onInstallButtonPressed: function(button, event) { _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 params = { shell_version: Config.PACKAGE_VERSION };
let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid); let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
let message = Soup.form_request_new_from_hash('GET', url, params); let message = Soup.form_request_new_from_hash('GET', url, params);
_httpSession.queue_message(message, let invocation = this._invocation;
Lang.bind(this, function(session, message) { function errback(code, message) {
gotExtensionZipFile(session, message, this._uuid); 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()); this.close(global.get_current_time());

View File

@ -202,6 +202,7 @@ const GnomeShellExtensionsIface = <interface name="org.gnome.Shell.Extensions">
</signal> </signal>
<method name="InstallRemoteExtension"> <method name="InstallRemoteExtension">
<arg type="s" direction="in" name="uuid"/> <arg type="s" direction="in" name="uuid"/>
<arg type="s" direction="out" name="result"/>
</method> </method>
<method name="UninstallExtension"> <method name="UninstallExtension">
<arg type="s" direction="in" name="uuid"/> <arg type="s" direction="in" name="uuid"/>
@ -285,8 +286,8 @@ const GnomeShellExtensions = new Lang.Class({
return extension.errors; return extension.errors;
}, },
InstallRemoteExtension: function(uuid) { InstallRemoteExtensionAsync: function([uuid], invocation) {
ExtensionDownloader.installExtensionFromUUID(uuid); return ExtensionDownloader.installExtensionFromUUID(uuid, invocation);
}, },
UninstallExtension: function(uuid) { UninstallExtension: function(uuid) {