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:
parent
1d1359b58f
commit
7949397958
@ -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);
|
||||||
|
|
||||||
|
@ -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());
|
||||||
|
@ -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) {
|
||||||
|
Loading…
Reference in New Issue
Block a user