diff --git a/js/misc/fileUtils.js b/js/misc/fileUtils.js index 411004156..ea80dc35b 100644 --- a/js/misc/fileUtils.js +++ b/js/misc/fileUtils.js @@ -20,3 +20,25 @@ function listDirAsync(file, callback) { enumerator.next_files_async(100, GLib.PRIORITY_LOW, null, onNextFileComplete); }); } + +function deleteGFile(file) { + // Work around 'delete' being a keyword in JS. + return file['delete'](null); +} + +function recursivelyDeleteDir(dir) { + let children = dir.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + + let info, child; + while ((info = children.next_file(null)) != null) { + let type = info.get_file_type(); + let child = dir.get_child(info.get_name()); + if (type == Gio.FileType.REGULAR) + deleteGFile(child); + else if (type == Gio.TypeType.DIRECTORY) + recursivelyDeleteDir(child); + } + + deleteGFile(dir); +} diff --git a/js/ui/extensionSystem.js b/js/ui/extensionSystem.js index 3dd4e1d0f..f88307047 100644 --- a/js/ui/extensionSystem.js +++ b/js/ui/extensionSystem.js @@ -10,6 +10,7 @@ const Shell = imports.gi.Shell; const Soup = imports.gi.Soup; const Config = imports.misc.config; +const FileUtils = imports.misc.fileUtils; const API_VERSION = 1; @@ -120,6 +121,37 @@ function installExtensionFromUUID(uuid, version_tag) { }); } +function uninstallExtensionFromUUID(uuid) { + let meta = extensionMeta[uuid]; + if (!meta) + return false; + + // Try to disable it -- if it's ERROR'd, we can't guarantee that, + // but it will be removed on next reboot, and hopefully nothing + // broke too much. + disableExtension(uuid); + + // Don't try to uninstall system extensions + if (meta.type != ExtensionType.PER_USER) + return false; + + meta.state = ExtensionState.UNINSTALLED; + _signals.emit('extension-state-changed', meta); + + delete extensionMeta[uuid]; + + // Importers are marked as PERMANENT, so we can't do this. + // delete extensions[uuid]; + extensions[uuid] = undefined; + + delete extensionStateObjs[uuid]; + delete errors[uuid]; + + FileUtils.recursivelyDeleteDir(Gio.file_new_for_path(meta.path)); + + return true; +} + function gotExtensionZipFile(session, message, uuid) { if (message.status_code != Soup.KnownStatusCode.OK) { logExtensionError(uuid, 'downloading extension: ' + message.status_code); diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index abc7466b9..ebf0c76af 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -48,6 +48,10 @@ const GnomeShellIface = { { name: 'InstallRemoteExtension', inSignature: 'ss', outSignature: '' + }, + { name: 'UninstallExtension', + inSignature: 's', + outSignature: 'b' } ], signals: [{ name: 'ExtensionStatusChanged', @@ -178,6 +182,10 @@ GnomeShell.prototype = { ExtensionSystem.installExtensionFromUUID(uuid, version_tag); }, + UninstallExtension: function(uuid) { + return ExtensionSystem.uninstallExtensionFromUUID(uuid); + }, + get OverviewActive() { return Main.overview.visible; },