From ca857851204b990c4d1c25216ef75db5eaef695d Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Wed, 28 Aug 2019 22:07:02 -0400 Subject: [PATCH 1/5] Add initial update mechanism --- Makefile | 2 +- extension.js | 3 + ...shell.extensions.dash-to-panel.gschema.xml | 3 + update.js | 174 ++++++++++++++++++ utils.js | 21 ++- 5 files changed, 201 insertions(+), 2 deletions(-) create mode 100644 update.js diff --git a/Makefile b/Makefile index 02aca8f..fad5383 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ UUID = dash-to-panel@jderose9.github.com BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md -EXTRA_MODULES = appIcons.js convenience.js panel.js panelManager.js proximity.js intellihide.js panelStyle.js overview.js taskbar.js transparency.js windowPreview.js prefs.js utils.js Settings.ui +EXTRA_MODULES = appIcons.js convenience.js panel.js panelManager.js proximity.js intellihide.js panelStyle.js overview.js taskbar.js transparency.js windowPreview.js prefs.js update.js utils.js Settings.ui EXTRA_IMAGES = highlight_stacked_bg.svg TOLOCALIZE = prefs.js appIcons.js MSGSRC = $(wildcard po/*.po) diff --git a/extension.js b/extension.js index ddabe0a..2b2f028 100644 --- a/extension.js +++ b/extension.js @@ -33,6 +33,7 @@ const Signals = imports.signals; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; const PanelManager = Me.imports.panelManager; +const Update = Me.imports.update; const Utils = Me.imports.utils; const UBUNTU_DOCK_UUID = 'ubuntu-dock@ubuntu.com'; @@ -90,6 +91,8 @@ function _enable() { panelManager = new PanelManager.dtpPanelManager(settings); panelManager.enable(); + settings.connect('changed::version-to-install', () => Update.installRelease(settings.get_string('version-to-install'))); + Utils.removeKeybinding('open-application-menu'); Utils.addKeybinding( 'open-application-menu', diff --git a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml index 5349a9d..58cfd7b 100644 --- a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml +++ b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml @@ -66,6 +66,9 @@ + + '' + 'BOTTOM' Panel position diff --git a/update.js b/update.js new file mode 100644 index 0000000..ec31533 --- /dev/null +++ b/update.js @@ -0,0 +1,174 @@ +/* + * This file is part of the Dash-To-Panel extension for Gnome 3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const Soup = imports.gi.Soup; +const FileUtils = imports.misc.fileUtils; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); +const _ = Gettext.gettext; + +const releaseApiUrl = 'https://api.github.com/repos/home-sweet-gnome/dash-to-panel/releases/'; + +let httpSession; + +function getLatestReleaseInfo(cb) { + getReleaseInfo('latest', cb); +} + +function getTaggedReleaseInfo(releaseTag, cb) { + getReleaseInfo('tags/' + releaseTag, cb); +} + +function getReleaseInfo(suffix, cb) { + let message = Soup.form_request_new_from_hash('GET', releaseApiUrl + suffix, {}); + + getHttpMessageResponseBody(message, (err, body) => { + if (err) { + return cb(err); + } + + try { + let release = JSON.parse(body.data); + let releaseInfo = { + name: release.name, + tag: release.tag_name, + zipUrl: (release.assets.length ? release.assets[0].browser_download_url : ''), + }; + + cb(null, releaseInfo); + } catch (e) { + cb(e.message); + } + }); +} + +function installRelease(releaseAssetUrl) { + getHttpMessageResponseBody(Soup.form_request_new_from_hash('GET', releaseAssetUrl, {}), (err, body) => { + if (err) { + return notifyInstallResult(err); + } + + let name = 'dtp_install'; + let zipFile = createTmp(name + '.zip'); + let stream = zipFile.replace(null, false, Gio.FileCreateFlags.NONE, null); + let extDir = Gio.file_new_for_path(Me.path); + let tmpDir = createTmp(name, true); + let bckDir = createTmp(Me.uuid, true); + + stream.write_bytes(body.flatten().get_as_bytes(), null); + stream.close(null); + + unzipFile(zipFile, tmpDir, err => { + if (err) { + return notifyInstallResult(err); + } + + try { + let [success, out, err, exitCode] = GLib.spawn_command_line_sync('pidof "gnome-shell-extension-prefs"'); + + FileUtils.recursivelyMoveDir(extDir, bckDir); + FileUtils.recursivelyMoveDir(tmpDir, extDir); + + if (success && !exitCode) { + GLib.spawn_command_line_sync('kill -9 ' + imports.byteArray.toString(out)); + } + } catch (e) { + FileUtils.recursivelyDeleteDir(extDir, false); + FileUtils.recursivelyMoveDir(bckDir, extDir); + + return notifyInstallResult(e.message); + } + + notifyInstallResult(null); + }); + }); +} + +function notifyInstallResult(err) { + let Meta = imports.gi.Meta; + let icon = 'information'; + let action = 0; + + if (err) { + var msg = _('Error: ') + err; + icon = 'error'; + } else if (Meta.is_wayland_compositor()) { + msg = _('Update successful, please log out/in'); + action = { text: _('Log out'), func: () => new imports.misc.systemActions.getDefault().activateLogout() }; + } else { + msg = _('Update successful, please restart GNOME Shell'); + action = { text: _('Restart GNOME Shell'), func: () => Meta.restart(_("Restarting GNOME Shell…")) }; + } + + Me.imports.utils.notify('Dash to Panel', msg, 'dialog-' + icon, action); +} + +function createTmp(name, isDir) { + let tmp = Gio.file_new_for_path('/tmp/' + name); + + if (isDir && tmp.query_exists(null)) { + FileUtils.recursivelyDeleteDir(tmp, false); + } + + return tmp; +} + +function unzipFile(zipFile, destDir, cb) { + let [success, pid] = GLib.spawn_async( + null, + ['unzip', '-uod', destDir.get_path(), '--', zipFile.get_path()], + null, + GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD | GLib.SpawnFlags.STDOUT_TO_DEV_NULL, + null + ); + + if (!success) { + return cb('unzip spawn error'); + } + + GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, (pid, status) => { + GLib.spawn_close_pid(pid); + + if (status != 0) { + return cb('extraction error') + } + + return cb(null); + }); +} + +function getHttpMessageResponseBody(message, cb) { + if (!httpSession) { + httpSession = new Soup.Session(); + httpSession.user_agent = Me.metadata.uuid; + } + + httpSession.queue_message(message, (httpSession, message) => { + try { + if (!message.response_body || !message.response_body.data) { + return cb('No data received'); + } + + return cb(null, message.response_body); + } catch (e) { + return cb(e.message); + } + }); +} diff --git a/utils.js b/utils.js index 205ea18..246a877 100644 --- a/utils.js +++ b/utils.js @@ -32,6 +32,7 @@ const Shell = imports.gi.Shell; const St = imports.gi.St; const Mainloop = imports.mainloop; const Main = imports.ui.main; +const MessageTray = imports.ui.messageTray; const Tweener = imports.ui.tweener; const Util = imports.misc.util; @@ -412,7 +413,25 @@ var activateSiblingWindow = function(windows, direction, startWindow) { if (windowIndex != nextWindowIndex) { Main.activateWindow(windows[nextWindowIndex]); } -} +}; + +var notify = function(title, text, iconName, action) { + let source = new MessageTray.SystemNotificationSource(); + let notification = new MessageTray.Notification(source, title, text); + + if (iconName) { + source.createIcon = function() { + return new St.Icon({ icon_name: iconName }); + }; + } + + if (action) { + notification.addAction(action.text, action.func); + } + + Main.messageTray.add(source); + source.notify(notification); +}; /* * This is a copy of the same function in utils.js, but also adjust horizontal scrolling From ea434b70b0d80ca619bb5767be5513a8f41e2bee Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Thu, 29 Aug 2019 22:07:35 -0400 Subject: [PATCH 2/5] Add settings to allow version updates --- Settings.ui | 141 ++++++++++++++++++ extension.js | 4 +- prefs.js | 10 ++ ...shell.extensions.dash-to-panel.gschema.xml | 6 + update.js | 96 +++++++++--- utils.js | 12 +- 6 files changed, 240 insertions(+), 29 deletions(-) diff --git a/Settings.ui b/Settings.ui index 9d92c57..cf8ecce 100644 --- a/Settings.ui +++ b/Settings.ui @@ -7503,6 +7503,147 @@ + + False + True + 1 + + + + + False + 20 + 0 + in + + + True + False + none + + + True + True + + + True + False + 12 + 12 + 12 + 12 + 6 + 32 + + + True + False + True + This allows you to update the extension directly from the GitHub repository. + True + True + 0 + + + + 0 + 1 + 2 + + + + + True + False + start + Updates + + + 0 + 0 + 2 + + + + + True + False + start + Periodically check for updates + + + 0 + 3 + + + + + True + False + end + 6 + + + True + True + end + center + + + False + True + 0 + + + + + Check now + True + True + True + end + center + + + False + True + 1 + + + + + 1 + 3 + + + + + True + False + 12 + <span weight="bold" color="#B42B30">Be aware, these releases might be unreviewed!</span> <a href="https://extensions.gnome.org/about/">Read more</a> + True + True + 0 + + + 0 + 2 + 2 + + + + + + + + + + + + False True diff --git a/extension.js b/extension.js index 2b2f028..cf92279 100644 --- a/extension.js +++ b/extension.js @@ -88,11 +88,11 @@ function _enable() { if (panelManager) return; //already initialized settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-panel'); + Update.init(settings); + panelManager = new PanelManager.dtpPanelManager(settings); panelManager.enable(); - settings.connect('changed::version-to-install', () => Update.installRelease(settings.get_string('version-to-install'))); - Utils.removeKeybinding('open-application-menu'); Utils.addKeybinding( 'open-application-menu', diff --git a/prefs.js b/prefs.js index fd4ac9f..5678b1a 100644 --- a/prefs.js +++ b/prefs.js @@ -34,6 +34,7 @@ const Convenience = Me.imports.convenience; const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); const _ = Gettext.gettext; const N_ = function(e) { return e }; +const Update = Me.imports.update; const SCALE_UPDATE_TIMEOUT = 500; const DEFAULT_PANEL_SIZES = [ 128, 96, 64, 48, 32, 24, 16 ]; @@ -1738,6 +1739,15 @@ const Settings = new Lang.Class({ } ); }); + + this._settings.bind('check-update', + this._builder.get_object('updates_check_switch'), + 'active', + Gio.SettingsBindFlags.DEFAULT); + + this._builder.get_object('updates_check_now_button').connect('clicked', widget => { + this._settings.set_boolean('force-check-update', true); + }); }, _setPreviewTitlePosition: function() { diff --git a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml index 58cfd7b..0a33b6c 100644 --- a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml +++ b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml @@ -66,6 +66,12 @@ + + false + + + false + '' diff --git a/update.js b/update.js index ec31533..5bef87a 100644 --- a/update.js +++ b/update.js @@ -24,22 +24,58 @@ const Me = imports.misc.extensionUtils.getCurrentExtension(); const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); const _ = Gettext.gettext; -const releaseApiUrl = 'https://api.github.com/repos/home-sweet-gnome/dash-to-panel/releases/'; +const apiUrl = 'https://api.github.com/repos/home-sweet-gnome/dash-to-panel/'; +const tagsApiUrl = apiUrl + 'tags'; +const releaseApiUrl = apiUrl + 'releases'; let httpSession; +function init(settings) { + settings.connect('changed::version-to-install', () => installRelease(settings.get_string('version-to-install'), true)); + settings.connect('changed::force-check-update', () => { + if (settings.get_boolean('force-check-update')) { + checkForUpdate(settings, true); + settings.set_boolean('force-check-update', false); + } + }); + + //check for update now, then every 4 hours + checkForUpdate(settings); + imports.mainloop.timeout_add(14400000, () => checkForUpdate(settings)); +} + +function checkForUpdate(settings, fromSettings) { + if (fromSettings || settings.get_boolean('check-update')) { + getLatestReleaseInfo((err, latestRelease) => { + if (err) { + return notifyError(err); + } + + let latestVersion = latestRelease.tag.substring(1); + + if (Me.metadata.version < latestVersion) { + notify( + _('Version %s (%s) is available').format(latestVersion, latestRelease.name), + [{ text: _('Details'), func: () => imports.misc.util.spawn(['xdg-open', latestRelease.url]) }, + { text: _('Update'), func: () => installRelease(latestRelease.zipUrl, fromSettings) }] + ); + } else if (fromSettings) { + notify(_('Already up to date')); + } + }); + } +} + function getLatestReleaseInfo(cb) { - getReleaseInfo('latest', cb); + getReleaseInfo('/latest', cb); } function getTaggedReleaseInfo(releaseTag, cb) { - getReleaseInfo('tags/' + releaseTag, cb); + getReleaseInfo('/tags/' + releaseTag, cb); } function getReleaseInfo(suffix, cb) { - let message = Soup.form_request_new_from_hash('GET', releaseApiUrl + suffix, {}); - - getHttpMessageResponseBody(message, (err, body) => { + getHttpMessageResponseBody(createGet(releaseApiUrl + suffix), (err, body) => { if (err) { return cb(err); } @@ -49,6 +85,7 @@ function getReleaseInfo(suffix, cb) { let releaseInfo = { name: release.name, tag: release.tag_name, + url: release.html_url, zipUrl: (release.assets.length ? release.assets[0].browser_download_url : ''), }; @@ -59,8 +96,8 @@ function getReleaseInfo(suffix, cb) { }); } -function installRelease(releaseAssetUrl) { - getHttpMessageResponseBody(Soup.form_request_new_from_hash('GET', releaseAssetUrl, {}), (err, body) => { +function installRelease(releaseAssetUrl, fromSettings) { + getHttpMessageResponseBody(createGet(releaseAssetUrl), (err, body) => { if (err) { return notifyInstallResult(err); } @@ -81,13 +118,15 @@ function installRelease(releaseAssetUrl) { } try { - let [success, out, err, exitCode] = GLib.spawn_command_line_sync('pidof "gnome-shell-extension-prefs"'); - FileUtils.recursivelyMoveDir(extDir, bckDir); FileUtils.recursivelyMoveDir(tmpDir, extDir); - if (success && !exitCode) { - GLib.spawn_command_line_sync('kill -9 ' + imports.byteArray.toString(out)); + if (fromSettings) { + let [success, out, err, exitCode] = GLib.spawn_command_line_sync('pidof "gnome-shell-extension-prefs"'); + + if (success && !exitCode) { + GLib.spawn_command_line_sync('kill -9 ' + imports.byteArray.toString(out)); + } } } catch (e) { FileUtils.recursivelyDeleteDir(extDir, false); @@ -102,22 +141,27 @@ function installRelease(releaseAssetUrl) { } function notifyInstallResult(err) { - let Meta = imports.gi.Meta; - let icon = 'information'; - let action = 0; - if (err) { - var msg = _('Error: ') + err; - icon = 'error'; - } else if (Meta.is_wayland_compositor()) { - msg = _('Update successful, please log out/in'); - action = { text: _('Log out'), func: () => new imports.misc.systemActions.getDefault().activateLogout() }; + notifyError(err); + } else if (imports.gi.Meta.is_wayland_compositor()) { + notify( + _('Update successful, please log out/in'), + { text: _('Log out'), func: () => new imports.misc.systemActions.getDefault().activateLogout() } + ); } else { - msg = _('Update successful, please restart GNOME Shell'); - action = { text: _('Restart GNOME Shell'), func: () => Meta.restart(_("Restarting GNOME Shell…")) }; + notify( + _('Update successful, please restart GNOME Shell'), + { text: _('Restart GNOME Shell'), func: () => imports.gi.Meta.restart(_("Restarting GNOME Shell…")) } + ); } +} - Me.imports.utils.notify('Dash to Panel', msg, 'dialog-' + icon, action); +function notifyError(err) { + Me.imports.utils.notify(_('Error: ') + err, 'dialog-error', null, true); +} + +function notify(msg, action) { + Me.imports.utils.notify(msg, 'dialog-information', action, !!action); } function createTmp(name, isDir) { @@ -154,6 +198,10 @@ function unzipFile(zipFile, destDir, cb) { }); } +function createGet(url, params) { + return Soup.form_request_new_from_hash('GET', url, params || {}); +} + function getHttpMessageResponseBody(message, cb) { if (!httpSession) { httpSession = new Soup.Session(); diff --git a/utils.js b/utils.js index 246a877..372215b 100644 --- a/utils.js +++ b/utils.js @@ -415,9 +415,9 @@ var activateSiblingWindow = function(windows, direction, startWindow) { } }; -var notify = function(title, text, iconName, action) { +var notify = function(text, iconName, action, isTransient) { let source = new MessageTray.SystemNotificationSource(); - let notification = new MessageTray.Notification(source, title, text); + let notification = new MessageTray.Notification(source, 'Dash to Panel', text); if (iconName) { source.createIcon = function() { @@ -426,10 +426,16 @@ var notify = function(title, text, iconName, action) { } if (action) { - notification.addAction(action.text, action.func); + if (!(action instanceof Array)) { + action = [action]; + } + + action.forEach(a => notification.addAction(a.text, a.func)); } Main.messageTray.add(source); + + notification.setTransient(isTransient); source.notify(notification); }; From 1eb41277d30aa1f5a588fbc1cbaff140c2590df3 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Fri, 30 Aug 2019 14:25:03 -0400 Subject: [PATCH 3/5] Set all update notifications as transient --- update.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/update.js b/update.js index 5bef87a..fea40d5 100644 --- a/update.js +++ b/update.js @@ -26,7 +26,7 @@ const _ = Gettext.gettext; const apiUrl = 'https://api.github.com/repos/home-sweet-gnome/dash-to-panel/'; const tagsApiUrl = apiUrl + 'tags'; -const releaseApiUrl = apiUrl + 'releases'; +const releasesApiUrl = apiUrl + 'releases'; let httpSession; @@ -75,7 +75,7 @@ function getTaggedReleaseInfo(releaseTag, cb) { } function getReleaseInfo(suffix, cb) { - getHttpMessageResponseBody(createGet(releaseApiUrl + suffix), (err, body) => { + getHttpMessageResponseBody(createGet(releasesApiUrl + suffix), (err, body) => { if (err) { return cb(err); } @@ -161,7 +161,7 @@ function notifyError(err) { } function notify(msg, action) { - Me.imports.utils.notify(msg, 'dialog-information', action, !!action); + Me.imports.utils.notify(msg, 'dialog-information', action, true); } function createTmp(name, isDir) { From d844d02d84fb0b22c3ac2f01c934d833d70ef713 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Sat, 31 Aug 2019 14:55:38 -0400 Subject: [PATCH 4/5] Check if the release zip is available --- update.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/update.js b/update.js index fea40d5..814db14 100644 --- a/update.js +++ b/update.js @@ -53,7 +53,7 @@ function checkForUpdate(settings, fromSettings) { let latestVersion = latestRelease.tag.substring(1); - if (Me.metadata.version < latestVersion) { + if (Me.metadata.version < latestVersion && latestRelease.zipUrl) { notify( _('Version %s (%s) is available').format(latestVersion, latestRelease.name), [{ text: _('Details'), func: () => imports.misc.util.spawn(['xdg-open', latestRelease.url]) }, @@ -86,7 +86,7 @@ function getReleaseInfo(suffix, cb) { name: release.name, tag: release.tag_name, url: release.html_url, - zipUrl: (release.assets.length ? release.assets[0].browser_download_url : ''), + zipUrl: (release.assets.length ? release.assets[0].browser_download_url : 0), }; cb(null, releaseInfo); From 9f0ccd76aa2dbd905b7bcd24619172b7450c8c77 Mon Sep 17 00:00:00 2001 From: Charles Gagnon Date: Sat, 31 Aug 2019 15:04:44 -0400 Subject: [PATCH 5/5] Try to clarify the meaning of an unreviewed status --- Settings.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Settings.ui b/Settings.ui index cf8ecce..550f918 100644 --- a/Settings.ui +++ b/Settings.ui @@ -7623,7 +7623,7 @@ True False 12 - <span weight="bold" color="#B42B30">Be aware, these releases might be unreviewed!</span> <a href="https://extensions.gnome.org/about/">Read more</a> + <span weight="bold" color="#B42B30">Be aware, these official Dash to Panel releases might not be reviewed yet on extensions.gnome.org!</span> <a href="https://extensions.gnome.org/about/">Read more</a> True True 0