From 1e286e43ada7f20214890d5d9d3c3a5cb837e4f4 Mon Sep 17 00:00:00 2001 From: "Jasper St. Pierre" Date: Tue, 26 Jun 2012 20:47:44 -0400 Subject: [PATCH] extensionDownloader: Add update/blacklist support for extensions This is a bare-bones copy/replace. It does not implement ChangeLog support. If we cannot get System Updates integration, I will implement notification support. https://bugzilla.gnome.org/show_bug.cgi?id=679099 --- js/misc/fileUtils.js | 27 +++++++++-- js/ui/extensionDownloader.js | 87 +++++++++++++++++++++++++++++++++--- js/ui/shellDBus.js | 6 +++ 3 files changed, 112 insertions(+), 8 deletions(-) diff --git a/js/misc/fileUtils.js b/js/misc/fileUtils.js index 7466be87a..4e620254b 100644 --- a/js/misc/fileUtils.js +++ b/js/misc/fileUtils.js @@ -28,7 +28,7 @@ function deleteGFile(file) { return file['delete'](null); } -function recursivelyDeleteDir(dir) { +function recursivelyDeleteDir(dir, deleteParent) { let children = dir.enumerate_children('standard::name,standard::type', Gio.FileQueryInfoFlags.NONE, null); @@ -39,8 +39,29 @@ function recursivelyDeleteDir(dir) { if (type == Gio.FileType.REGULAR) deleteGFile(child); else if (type == Gio.FileType.DIRECTORY) - recursivelyDeleteDir(child); + recursivelyDeleteDir(child, true); } - deleteGFile(dir); + if (deleteParent) + deleteGFile(dir); +} + +function recursivelyMoveDir(srcDir, destDir) { + let children = srcDir.enumerate_children('standard::name,standard::type', + Gio.FileQueryInfoFlags.NONE, null); + + if (!destDir.query_exists(null)) + destDir.make_directory_with_parents(null); + + let info, child; + while ((info = children.next_file(null)) != null) { + let type = info.get_file_type(); + let srcChild = srcDir.get_child(info.get_name()); + let destChild = destDir.get_child(info.get_name()); + log([srcChild.get_path(), destChild.get_path()]); + if (type == Gio.FileType.REGULAR) + srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null); + else if (type == Gio.FileType.DIRECTORY) + recursivelyMoveDir(srcChild, destChild); + } } diff --git a/js/ui/extensionDownloader.js b/js/ui/extensionDownloader.js index 4dd683d89..83f8a36fc 100644 --- a/js/ui/extensionDownloader.js +++ b/js/ui/extensionDownloader.js @@ -7,6 +7,7 @@ const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; const Soup = imports.gi.Soup; const St = imports.gi.St; +const Shell = imports.gi.Shell; const Config = imports.misc.config; const ExtensionUtils = imports.misc.extensionUtils; @@ -18,7 +19,8 @@ const _signals = ExtensionSystem._signals; const REPOSITORY_URL_BASE = 'https://extensions.gnome.org'; const REPOSITORY_URL_DOWNLOAD = REPOSITORY_URL_BASE + '/download-extension/%s.shell-extension.zip'; -const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/'; +const REPOSITORY_URL_INFO = REPOSITORY_URL_BASE + '/extension-info/'; +const REPOSITORY_URL_UPDATE = REPOSITORY_URL_BASE + '/update-info/'; let _httpSession; @@ -61,17 +63,16 @@ function uninstallExtension(uuid) { if (!ExtensionSystem.unloadExtension(uuid)) return false; - FileUtils.recursivelyDeleteDir(extension.dir); + FileUtils.recursivelyDeleteDir(extension.dir, true); return true; } -function gotExtensionZipFile(session, message, uuid, callback, errback) { +function gotExtensionZipFile(session, message, uuid, dir, callback, errback) { if (message.status_code != Soup.KnownStatusCode.OK) { errback('DownloadExtensionError', message.status_code); return; } - let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid])); try { if (!dir.query_exists(null)) dir.make_directory_with_parents(null); @@ -105,6 +106,81 @@ function gotExtensionZipFile(session, message, uuid, callback, errback) { }); } +function updateExtension(uuid) { + // This gets a bit tricky. We want the update to be seamless - + // if we have any error during downloading or extracting, we + // want to not unload the current version. + + let oldExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension'); + let newExtensionTmpDir = GLib.Dir.make_tmp('XXXXXX-shell-extension'); + + let params = { shell_version: Config.PACKAGE_VERSION }; + + let url = REPOSITORY_URL_DOWNLOAD.format(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, uuid, newExtensionTmpDir, function() { + let oldExtension = ExtensionUtils.extensions[uuid]; + let extensionDir = oldExtension.dir; + + if (!ExtensionSystem.unloadExtension(uuid)) + return; + + FileUtils.recursivelyMoveDir(extensionDir, oldExtensionTmpDir); + FileUtils.recursivelyMoveDir(newExtensionTmpDir, extensionDir); + + let extension = ExtensionUtils.createExtensionObject(uuid, extensionDir, ExtensionUtils.ExtensionType.PER_USER); + + try { + ExtensionSystem.loadExtension(extension); + } catch(e) { + ExtensionSystem.unloadExtension(uuid); + + logError(e, 'Error loading extension %s'.format(uuid)); + + FileUtils.recursivelyDeleteDir(extensionDir, false); + FileUtils.recursivelyMoveDir(oldExtensionTmpDir, extensionDir); + + // Restore what was there before. We can't do much if we + // fail here. + ExtensionSystem.loadExtension(oldExtension); + return; + } + + FileUtils.recursivelyDeleteDir(oldExtensionTmpDir, true); + }, function(code, message) { + log('Error while updating extension %s: %s (%s)'.format(uuid, code, message ? message : '')); + }); + })); +} + +function checkForUpdates() { + let metadatas = {}; + for (let uuid in ExtensionUtils.extensions) { + metadatas[uuid] = ExtensionUtils.extensions[uuid].metadata; + } + + let params = { shell_version: Config.PACKAGE_VERSION, + installed: JSON.stringify(metadatas) }; + + let url = REPOSITORY_URL_UPDATE; + let message = Soup.form_request_new_from_hash('GET', url, params); + _httpSession.queue_message(message, function(session, message) { + if (message.status_code != Soup.KnownStatusCode.OK) + return; + + let operations = JSON.parse(message.response_body.data); + for (let uuid in operations) { + let operation = operations[uuid]; + if (operation == 'blacklist') + uninstallExtension(uuid); + else if (operation == 'upgrade' || operation == 'downgrade') + updateExtension(uuid); + } + }); +} + const InstallExtensionDialog = new Lang.Class({ Name: 'InstallExtensionDialog', Extends: ModalDialog.ModalDialog, @@ -149,6 +225,7 @@ const InstallExtensionDialog = new Lang.Class({ let message = Soup.form_request_new_from_hash('GET', url, params); let uuid = this._uuid; + let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid])); let invocation = this._invocation; function errback(code, message) { invocation.return_dbus_error('org.gnome.Shell.' + code, message ? message.toString() : ''); @@ -176,7 +253,7 @@ const InstallExtensionDialog = new Lang.Class({ } _httpSession.queue_message(message, Lang.bind(this, function(session, message) { - gotExtensionZipFile(session, message, uuid, callback, errback); + gotExtensionZipFile(session, message, uuid, dir, callback, errback); })); this.close(global.get_current_time()); diff --git a/js/ui/shellDBus.js b/js/ui/shellDBus.js index 83074e4ba..661818ed8 100644 --- a/js/ui/shellDBus.js +++ b/js/ui/shellDBus.js @@ -214,6 +214,8 @@ const GnomeShellExtensionsIface = + + ; @@ -306,6 +308,10 @@ const GnomeShellExtensions = new Lang.Class({ ExtensionSystem.loadExtension(uuid); }, + CheckForUpdates: function() { + ExtensionDownloader.checkForUpdates(); + }, + ShellVersion: Config.PACKAGE_VERSION, _extensionStateChanged: function(_, newState) {