diff -Nru software-properties-0.96.24.32.1/add-apt-repository software-properties-0.96.24.32.14/add-apt-repository --- software-properties-0.96.24.32.1/add-apt-repository 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/add-apt-repository 2020-08-07 14:07:37.000000000 +0000 @@ -4,6 +4,7 @@ import io import os +import re import sys import gettext import locale @@ -146,7 +147,11 @@ print(e) sys.exit(1) - print(" %s" % (info["description"] or "")) + # strip ANSI escape sequences + description = re.sub(r"(\x9B|\x1B\[)[0-?]*[ -/]*[@-~]", + "", info["description"] or "") + + print(" %s" % description) print(_(" More info: %s") % str(info["web_link"])) if (sys.stdin.isatty() and not "FORCE_ADD_APT_REPOSITORY" in os.environ): diff -Nru software-properties-0.96.24.32.1/data/gtkbuilder/dialog-auth.ui software-properties-0.96.24.32.14/data/gtkbuilder/dialog-auth.ui --- software-properties-0.96.24.32.1/data/gtkbuilder/dialog-auth.ui 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/data/gtkbuilder/dialog-auth.ui 2019-07-12 13:59:10.000000000 +0000 @@ -1,114 +1,130 @@ - + + + + + + + + + + + - False False - True - True dialog - False + + + - - False + + 6 vertical 2 - - False + + + + Add another… + True + True + True + + + + True + True + True + + + + + Cancel + True + True + True + + + + True + True + + + + + True + True + True + + + + True + True + + - False - False - 0 + True - + True - False + start 12 - 12 - 12 + 18 - + True - False - True - To enable Livepatch choose an Ubuntu Single Sign-on account. - True - 0 + start + start + software-properties + 6 - - 0 - 0 - - + True - False - 0 + start + vertical + 12 - + + label_header True - False - none + start + start + True + fill + True + 40 + + + + + start + liststore_account - - True - False - - - 48 - True - False - center - center - <b>Use another account...</b> - True - center - - - + + + 1 + + + + start + + - - 0 - 1 - 2 - - - False - True - 1 - - - - - - - True - False - Choose an account - - - gtk-cancel - True - True - True - True - - - button_cancel - + diff -Nru software-properties-0.96.24.32.1/data/gtkbuilder/dialog-livepatch-error.ui software-properties-0.96.24.32.14/data/gtkbuilder/dialog-livepatch-error.ui --- software-properties-0.96.24.32.1/data/gtkbuilder/dialog-livepatch-error.ui 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/data/gtkbuilder/dialog-livepatch-error.ui 2019-07-12 13:59:10.000000000 +0000 @@ -1,58 +1,139 @@ - + - - False + + + Livepatch + False + True dialog - error - Sorry, there’s been a problem in setting up Canonical Livepatch. + True + False + True + True - - False + vertical 2 - - False + end Settings… - True True - True + False True True - 0 Ignore True + True True True - 0.51999998092651367 True True - 1 False False - 0 + + + + + True + 12 + vertical + + + True + 12 + 12 + + + True + 0 + + + 1 + 0 + + + + + True + center + start + gtk-dialog-error + True + 6 + + + 0 + 0 + 3 + + + + + True + The error was: + 0 + + + 1 + 1 + + + + + 100 + True + True + True + 6 + 6 + False + word + 6 + 6 + False + textbuffer_message + False + + + 1 + 2 + + + + + True + True + + + + + True + True + + + diff -Nru software-properties-0.96.24.32.1/data/gtkbuilder/main.ui software-properties-0.96.24.32.14/data/gtkbuilder/main.ui --- software-properties-0.96.24.32.1/data/gtkbuilder/main.ui 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/data/gtkbuilder/main.ui 2019-07-12 13:59:10.000000000 +0000 @@ -1,7 +1,13 @@ - + - + + + + + + + @@ -91,6 +97,7 @@ To install from a CD-ROM or DVD, insert the medium into the drive. + False 6 @@ -696,84 +703,6 @@ - - True - False - 12 - - - True - False - center - False - False - 6 - 6 - - - True - False - center - 6 - - - True - False - - - False - True - 0 - - - - - True - True - True - 0 - - - False - True - 2 - - - - - 1 - 1 - - - - - Use Canonical Livepatch to increase security between restarts - False - True - True - False - True - 0 - True - - - 1 - 0 - - - - - - - - - - True - True - 2 - - - True False @@ -820,7 +749,7 @@ False False - 3 + 2 @@ -1187,6 +1116,231 @@ False + + + True + False + 12 + vertical + 12 + + + True + False + Canonical Livepatch helps keep your system secure by applying security updates that don't require a restart. <a href="https://www.ubuntu.com/livepatch">Learn More</a> + True + True + 1 + 0 + + + False + True + 0 + + + + + True + False + 6 + + + True + False + True + + + False + True + 0 + + + + + False + + + False + True + 1 + + + + + True + False + + + False + True + 2 + + + + + True + True + + + False + True + end + 3 + + + + + False + True + 1 + + + + + False + crossfade + True + + + True + True + in + + + True + True + 6 + False + word + 6 + 6 + False + textbuffer_livepatch + False + + + + + page_livepatch_message + + + + + True + False + vertical + 12 + + + True + False + 0 + + + False + True + 0 + + + + + True + False + 0 + + + False + True + 1 + + + + + True + True + in + + + True + True + model_livepatch_fixes + False + False + False + + + + + + column + + + 100 + word + 100 + + + 0 + + + + + + + + + True + True + 2 + + + + + page_livepatch_status + 1 + + + + + True + True + 2 + + + + + Show Livepatch status in the top bar + True + False + True + False + start + True + + + False + True + end + 3 + + + + + 6 + + + + + True + False + Livepatch + + + 6 + False + + True @@ -1246,6 +1400,9 @@ + + + Binary files /tmp/tmpuHEo32/yyQuJywu_O/software-properties-0.96.24.32.1/data/icons/16x16/apps/livepatch.svg and /tmp/tmpuHEo32/WUNoDo604_/software-properties-0.96.24.32.14/data/icons/16x16/apps/livepatch.svg differ Binary files /tmp/tmpuHEo32/yyQuJywu_O/software-properties-0.96.24.32.1/data/icons/24x24/apps/livepatch.svg and /tmp/tmpuHEo32/WUNoDo604_/software-properties-0.96.24.32.14/data/icons/24x24/apps/livepatch.svg differ Binary files /tmp/tmpuHEo32/yyQuJywu_O/software-properties-0.96.24.32.1/data/icons/48x48/apps/livepatch.svg and /tmp/tmpuHEo32/WUNoDo604_/software-properties-0.96.24.32.14/data/icons/48x48/apps/livepatch.svg differ Binary files /tmp/tmpuHEo32/yyQuJywu_O/software-properties-0.96.24.32.1/data/icons/64x64/apps/livepatch.svg and /tmp/tmpuHEo32/WUNoDo604_/software-properties-0.96.24.32.14/data/icons/64x64/apps/livepatch.svg differ diff -Nru software-properties-0.96.24.32.1/data/icons/scalable/apps/livepatch.svg software-properties-0.96.24.32.14/data/icons/scalable/apps/livepatch.svg --- software-properties-0.96.24.32.1/data/icons/scalable/apps/livepatch.svg 1970-01-01 00:00:00.000000000 +0000 +++ software-properties-0.96.24.32.14/data/icons/scalable/apps/livepatch.svg 2019-07-12 13:59:10.000000000 +0000 @@ -0,0 +1 @@ + \ No newline at end of file diff -Nru software-properties-0.96.24.32.1/data/software-properties-livepatch.desktop.in software-properties-0.96.24.32.14/data/software-properties-livepatch.desktop.in --- software-properties-0.96.24.32.1/data/software-properties-livepatch.desktop.in 1970-01-01 00:00:00.000000000 +0000 +++ software-properties-0.96.24.32.14/data/software-properties-livepatch.desktop.in 2019-07-12 13:59:10.000000000 +0000 @@ -0,0 +1,12 @@ +[Desktop Entry] +Keywords=Livepatch; +Exec=/usr/bin/software-properties-gtk --open-tab=6 +Icon=livepatch +Terminal=false +Type=Application +OnlyShowIn=GNOME; +Categories=GTK;Settings;HardwareSettings +X-AppStream-Ignore=true +Name=Livepatch +_Comment=Manage Canonical Livepatch +_Keywords=Security;Update; diff -Nru software-properties-0.96.24.32.1/debian/changelog software-properties-0.96.24.32.14/debian/changelog --- software-properties-0.96.24.32.1/debian/changelog 2018-04-19 06:57:15.000000000 +0000 +++ software-properties-0.96.24.32.14/debian/changelog 2020-08-07 14:07:43.000000000 +0000 @@ -1,3 +1,117 @@ +software-properties (0.96.24.32.14) bionic-security; urgency=medium + + * SECURITY UPDATE: malicious repo could send ANSI sequences to terminal + (LP: #1890286) + - add-apt-repository: strip ANSI sequences from the description. + - CVE-2020-15709 + + -- Marc Deslauriers Fri, 07 Aug 2020 10:07:43 -0400 + +software-properties (0.96.24.32.13) bionic; urgency=medium + + * softwareproperties/gtk/SoftwarePropertiesGtk.py: + - Gtk is doing something that takes a lock while processing checkbox + toggled events, the polkit auth dialog can't be displayed in return, + we don't have a proper fix and it might require GTK changes, + meanwhile we want a working interface so workaround with a sleep + (lp: #1727908) + + -- Sebastien Bacher Fri, 08 May 2020 16:47:19 +0200 + +software-properties (0.96.24.32.12) bionic; urgency=medium + + * cloudarchive: Enable support for the Ussuri Ubuntu Cloud Archive on + 18.04 (LP: #1852489). + + -- Corey Bryant Mon, 02 Dec 2019 09:21:38 -0500 + +software-properties (0.96.24.32.11) bionic; urgency=medium + + * cloudarchive: Update outdated WEB_LINK that is presented when + cloud archive is enabled (LP: #1836258). + + -- Corey Bryant Fri, 12 Jul 2019 10:10:04 -0400 + +software-properties (0.96.24.32.10) bionic; urgency=medium + + [ Andrea Azzarone ] + * Livepatch: Handle applying state, it's transient by not dealing with it + can lead to refresh issues (lp: #1835911) + * Livepatch: Add a checkbutton to hide/show the indicator in the top bar. + (lp: #1835910) + + -- Sebastien Bacher Tue, 09 Jul 2019 15:16:10 +0200 + +software-properties (0.96.24.32.9) bionic; urgency=medium + + * debian/control: + - required snapd-glib 1.42 (lp: #1830353) + + [ Iain Lane ] + * softwareproperties/gtk/LivepatchPage.py: + - livepatch: Always pass a string to SetLivePatchEnabled (lp: #1830348) + + [ Corey Bryant ] + * cloudarchive: Enable support for the Train Ubuntu Cloud Archive on + 18.04 (LP: #1829017). + + -- Sebastien Bacher Fri, 24 May 2019 17:03:01 +0200 + +software-properties (0.96.24.32.8) bionic; urgency=medium + + * debian/control: Update Vcs-*: URLs + * debian/gbp.conf: Add default configuration + * Add .keep files to preserve empty directories + * Backport Livepatch changes from Disco (LP: #1823761): + - Implement new design for authentication dialog. + - Add livepatch desktop file and icon. + - Move Livepatch UI in a diffrent tab. + + -- Andrea Azzarone Mon, 08 Apr 2019 16:52:15 +0100 + +software-properties (0.96.24.32.7) bionic; urgency=medium + + * SoftwarePropertiesGtk.py: when checking a package's depends for DKMS also + pass on an AttributeError (LP: #1807373) + + -- Brian Murray Fri, 14 Dec 2018 11:13:07 -0800 + +software-properties (0.96.24.32.6) bionic; urgency=medium + + * cloudarchive: Enable support for the Stein Ubuntu Cloud Archive on + 18.04 (LP: #1805436). + + -- Corey Bryant Tue, 27 Nov 2018 09:02:22 -0500 + +software-properties (0.96.24.32.5) bionic; urgency=medium + + * SoftwarePropertiesGtk.py: Hide livepatch widgets in flavors without + an online account panel in gnome-control-center (LP: #1770686) + + -- Andrea Azzarone Fri, 17 Aug 2018 09:56:07 +0000 + +software-properties (0.96.24.32.4) bionic; urgency=medium + + [ Andrea Azzarone ] + * Allow the user to disable livepatch if the gnome-online-account expired. + (lp: #1768797) + + -- Sebastien Bacher Wed, 04 Jul 2018 15:38:17 +0200 + +software-properties (0.96.24.32.3) bionic; urgency=medium + + * cloudarchive: Enable support for the Rocky Ubuntu Cloud Archive on + 18.04 (LP: #1769920). + + -- Corey Bryant Tue, 08 May 2018 10:58:48 -0400 + +software-properties (0.96.24.32.2) bionic; urgency=medium + + * SoftwarePropertiesGtk.py: uninstall the actual nvidia packages, + not only the meta-package (LP: #1753333). + + -- Alberto Milone Wed, 02 May 2018 15:58:01 +0200 + software-properties (0.96.24.32.1) bionic; urgency=medium * debian/control.in: diff -Nru software-properties-0.96.24.32.1/debian/control software-properties-0.96.24.32.14/debian/control --- software-properties-0.96.24.32.1/debian/control 2018-04-18 14:47:35.000000000 +0000 +++ software-properties-0.96.24.32.14/debian/control 2019-07-12 14:08:41.000000000 +0000 @@ -7,13 +7,17 @@ libxml-parser-perl, intltool, dbus-x11 , - gir1.2-snapd-1 , + gir1.2-gtk-3.0 , + gir1.2-snapd-1 (>= 1.42) , lsb-release , pyflakes3 , python3-apt , + python3-dateutil , python3-dbus , + python3-distro-info , python3-gi , python3-mock , + python3-requests-unixsocket , xauth , xvfb , python3-all, @@ -22,7 +26,7 @@ dh-migrations, dh-translations Standards-Version: 3.9.6 -Vcs-Bzr: http://code.launchpad.net/~ubuntu-core-dev/software-properties/main +Vcs-Git: https://git.launchpad.net/software-properties -b ubuntu/bionic XS-Testsuite: autopkgtest Package: python3-software-properties @@ -58,10 +62,11 @@ python3-gi, gir1.2-gtk-3.0, gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1), - gir1.2-secret-1, - gir1.2-snapd-1, + gir1.2-snapd-1 (>= 1.42), python3-aptdaemon.gtk3widgets, + python3-dateutil, python3-distro-info, + python3-requests-unixsocket, software-properties-common, ubuntu-drivers-common (>= 1:0.2.75), python3-gi, diff -Nru software-properties-0.96.24.32.1/debian/gbp.conf software-properties-0.96.24.32.14/debian/gbp.conf --- software-properties-0.96.24.32.1/debian/gbp.conf 1970-01-01 00:00:00.000000000 +0000 +++ software-properties-0.96.24.32.14/debian/gbp.conf 2019-07-12 14:08:41.000000000 +0000 @@ -0,0 +1,3 @@ +[DEFAULT] +debian-branch = ubuntu/bionic +debian-tag = %(version)s diff -Nru software-properties-0.96.24.32.1/debian/software-properties-gtk.install software-properties-0.96.24.32.14/debian/software-properties-gtk.install --- software-properties-0.96.24.32.1/debian/software-properties-gtk.install 2018-04-17 16:20:41.000000000 +0000 +++ software-properties-0.96.24.32.14/debian/software-properties-gtk.install 2019-07-12 14:08:41.000000000 +0000 @@ -5,6 +5,7 @@ debian/tmp/usr/share/icons debian/tmp/usr/share/applications/software-properties-gtk.desktop debian/tmp/usr/share/applications/software-properties-drivers.desktop +debian/tmp/usr/share/applications/software-properties-livepatch.desktop debian/tmp/usr/share/metainfo/software-properties-gtk.appdata.xml debian/tmp/usr/share/glib-2.0/schemas #debian/tmp/usr/share/gnome/help/software-properties diff -Nru software-properties-0.96.24.32.1/po/POTFILES.in software-properties-0.96.24.32.14/po/POTFILES.in --- software-properties-0.96.24.32.1/po/POTFILES.in 2018-04-17 16:02:47.000000000 +0000 +++ software-properties-0.96.24.32.14/po/POTFILES.in 2019-07-12 14:08:41.000000000 +0000 @@ -5,6 +5,7 @@ data/software-properties-gtk.appdata.xml.in data/software-properties-kde.desktop.in data/software-properties-drivers.desktop.in +data/software-properties-livepatch.desktop.in software-properties-gtk software-properties-kde add-apt-repository @@ -28,8 +29,12 @@ softwareproperties/gtk/DialogEdit.py softwareproperties/gtk/DialogAdd.py softwareproperties/gtk/DialogCacheOutdated.py +softwareproperties/gtk/DialogLivepatchError.py +softwareproperties/gtk/LivepatchPage.py softwareproperties/CountryInformation.py softwareproperties/AptAuth.py +softwareproperties/LivepatchService.py +softwareproperties/LivepatchSnap.py [type: gettext/glade]data/designer/dialog_mirror.ui [type: gettext/glade]data/designer/dialog_edit.ui [type: gettext/glade]data/designer/main.ui @@ -41,3 +46,4 @@ [type: gettext/glade]data/gtkbuilder/dialog-mirror.ui [type: gettext/glade]data/gtkbuilder/dialog-add.ui [type: gettext/glade]data/gtkbuilder/dialog-auth.ui +[type: gettext/glade]data/gtkbuilder/dialog-livepatch-error.ui diff -Nru software-properties-0.96.24.32.1/setup.cfg software-properties-0.96.24.32.14/setup.cfg --- software-properties-0.96.24.32.1/setup.cfg 2018-04-17 16:02:47.000000000 +0000 +++ software-properties-0.96.24.32.14/setup.cfg 2019-07-12 14:08:41.000000000 +0000 @@ -4,6 +4,7 @@ desktop_files=[("share/applications", ("data/software-properties-gtk.desktop.in", "data/software-properties-drivers.desktop.in", + "data/software-properties-livepatch.desktop.in", "data/software-properties-kde.desktop.in",), ) ] diff -Nru software-properties-0.96.24.32.1/softwareproperties/cloudarchive.py software-properties-0.96.24.32.14/softwareproperties/cloudarchive.py --- software-properties-0.96.24.32.1/softwareproperties/cloudarchive.py 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/cloudarchive.py 2019-12-02 14:21:38.000000000 +0000 @@ -41,10 +41,14 @@ 'ocata': 'xenial', 'pike': 'xenial', 'queens': 'xenial', + 'rocky': 'bionic', + 'stein': 'bionic', + 'train': 'bionic', + 'ussuri': 'bionic', } MIRROR = "http://ubuntu-cloud.archive.canonical.com/ubuntu" UCA = "Ubuntu Cloud Archive" -WEB_LINK = 'https://wiki.ubuntu.com/ServerTeam/CloudArchive' +WEB_LINK = 'https://wiki.ubuntu.com/OpenStack/CloudArchive' APT_INSTALL_KEY = ['apt-get', '--quiet', '--assume-yes', 'install', 'ubuntu-cloud-keyring'] diff -Nru software-properties-0.96.24.32.1/softwareproperties/dbus/SoftwarePropertiesDBus.py software-properties-0.96.24.32.14/softwareproperties/dbus/SoftwarePropertiesDBus.py --- software-properties-0.96.24.32.1/softwareproperties/dbus/SoftwarePropertiesDBus.py 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/dbus/SoftwarePropertiesDBus.py 2019-07-12 14:08:41.000000000 +0000 @@ -25,10 +25,12 @@ import subprocess import tempfile import sys +import threading from aptsources.sourceslist import SourceEntry from dbus.mainloop.glib import DBusGMainLoop +from softwareproperties.LivepatchService import LivepatchService from softwareproperties.SoftwareProperties import SoftwareProperties DBUS_BUS_NAME = 'com.ubuntu.SoftwareProperties' @@ -61,7 +63,7 @@ self.enforce_polkit = True logging.debug("waiting for connections") - self.init_snapd() + self._livepatch_service = LivepatchService() # override set_modified_sourceslist to emit a signal def save_sourceslist(self): @@ -324,9 +326,13 @@ sender_keyword="sender", connection_keyword="conn", in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler')) def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None): + def enable_thread_func(): + ret = self._livepatch_service.set_enabled(enabled, token) + GLib.idle_add(lambda: reply_handler(*ret)) + self._check_policykit_privilege( sender, conn, "com.ubuntu.softwareproperties.applychanges") - self.set_livepatch_enabled_async(enabled, token, reply_handler) + threading.Thread(target=enable_thread_func).start() # helper from jockey def _check_policykit_privilege(self, sender, conn, privilege): diff -Nru software-properties-0.96.24.32.1/softwareproperties/GoaAuth.py software-properties-0.96.24.32.14/softwareproperties/GoaAuth.py --- software-properties-0.96.24.32.1/softwareproperties/GoaAuth.py 2018-04-17 16:20:41.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/GoaAuth.py 2019-07-12 13:59:10.000000000 +0000 @@ -21,7 +21,9 @@ import gi gi.require_version('Goa', '1.0') -from gi.repository import Gio, Goa, GObject +from gi.repository import Gio, GLib, Goa, GObject + +import logging class GoaAuth(GObject.GObject): @@ -32,12 +34,21 @@ def __init__(self): GObject.GObject.__init__(self) - self.goa_client = Goa.Client.new_sync(None) self.account = None + self.cancellable = Gio.Cancellable() + Goa.Client.new(self.cancellable, self._on_goa_client_ready) self.settings = Gio.Settings.new('com.ubuntu.SoftwareProperties') self.settings.connect('changed::goa-account-id', self._on_settings_changed) - self._load() + + def _on_goa_client_ready(self, source, res): + try: + self.goa_client = Goa.Client.new_finish(res) + except GLib.Error as e: + logging.error('Failed to get a Gnome Online Account: {}'.format(e.message)) + self.goa_client = None + else: + self._load() def login(self, account): assert(account) @@ -50,7 +61,7 @@ @GObject.Property def token(self): - if self.account is None: + if self.account is None or self.goa_client is None: return None obj = self.goa_client.lookup_by_id(self.account.props.id) @@ -64,7 +75,7 @@ return pbased.call_get_password_sync('livepatch') def _update_state_from_account_id(self, account_id): - if account_id: + if account_id and self.goa_client is not None: # Make sure the account-id is valid obj = self.goa_client.lookup_by_id(account_id) if obj is None: diff -Nru software-properties-0.96.24.32.1/softwareproperties/gtk/DialogAuth.py software-properties-0.96.24.32.14/softwareproperties/gtk/DialogAuth.py --- software-properties-0.96.24.32.1/softwareproperties/gtk/DialogAuth.py 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/gtk/DialogAuth.py 2019-07-12 13:59:10.000000000 +0000 @@ -19,6 +19,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA +from enum import IntEnum import os from gettext import gettext as _ @@ -28,9 +29,13 @@ import gi gi.require_version('Goa', '1.0') -from gi.repository import Gio, GLib, Goa, GObject, Gtk +from gi.repository import Gio, GLib, Goa, Gtk import logging +class Column(IntEnum): + ID = 0 + MAIL = 1 + ACCOUNT = 2 class DialogAuth: @@ -38,68 +43,128 @@ """setup up the gtk dialog""" self.parent = parent - setup_ui(self, os.path.join(datadir, "gtkbuilder", - "dialog-auth.ui"), domain="software-properties") - self.label_title.set_max_width_chars(50) + setup_ui(self, os.path.join(datadir, "gtkbuilder", "dialog-auth.ui"), + domain="software-properties") self.dialog = self.dialog_auth - self.dialog.use_header_bar = True + self.dialog.set_title('') + self.dialog.set_deletable(False) self.dialog.set_transient_for(parent) - self.listboxrow_new_account.account = None + self.button_continue.grab_focus() self.account = None self.dispose_on_new_account = False self.goa_client = Goa.Client.new_sync(None) - self.listbox_accounts.connect('row-activated', self._listbox_accounts_row_activated_cb) + self._setup_model() + self._check_ui(select=False) # Be ready to other accounts self.goa_client.connect('account-added', self._account_added_cb) self.goa_client.connect('account-removed', self._account_removed_cb) - self._setup_listbox_accounts() - self._check_ui() - def run(self): res = self.dialog.run() self.dialog.hide() return res - def _check_ui(self): - rows = self.listbox_accounts.get_children() - has_accounts = len(rows) > 1 - - if has_accounts: - title = _('To continue choose an Ubuntu Single Sign-On account.') - new_account = _('Use another account…') + def _setup_model(self): + for obj in self.goa_client.get_accounts(): + self._add_account(obj.get_account(), select=False) + + def _set_header(self, label): + self.label_header.set_markup( + "%s" % label) + + def _check_ui(self, select): + naccounts = len(self.liststore_account) + + if naccounts == 0: + self._set_header( + _('To use Livepatch, you need to use an Ubuntu One account.')) + self.combobox_account.set_visible(False) + self.label_account.set_visible(False) + self.button_add_another.set_visible(False) + self.button_continue.set_label(_('Sign In / Register…')) + elif naccounts == 1: + self._set_header( + _('To use Livepatch, you need to use your Ubuntu One account.')) + self.combobox_account.set_visible(False) + self.label_account.set_visible(True) + self.label_account.set_text(self.liststore_account[0][Column.MAIL]) + self.button_add_another.set_visible(True) + self.button_continue.set_label(_('Continue')) else: - title = _('To continue you need an Ubuntu Single Sign-On account.') - new_account = _('Sign In…') + self._set_header( + _('To use Livepatch, you need to use an Ubuntu One account.')) + self.button_add_another.set_visible(True) + self.combobox_account.set_visible(True) + self.label_account.set_visible(False) + self.button_continue.set_label(_('Use')) + if select: + self.combobox_account.set_active(naccounts-1) + elif self.combobox_account.get_active() == -1: + self.combobox_account.set_active(0) + + def _ignore_account(self, account): + return account.props.provider_type != 'ubuntusso' + + def _get_account_iter(self, account): + row = self.liststore_account.get_iter_first() + while row is not None: + account_id = self.liststore_account.get_value(row, Column.ID) + if account_id == account.props.id: + return row + row = self.liststore_account.iter_next(row) + return None - self.label_title.set_text(title) - self.label_new_account.set_markup('{}'.format(new_account)) + def _add_account(self, account, select): + if self._ignore_account(account): + return - def _setup_listbox_accounts(self): - for obj in self.goa_client.get_accounts(): - account = obj.get_account() - if self._is_account_supported(account): - self._add_account(account) - - def _is_account_supported(self, account): - return account.props.provider_type == 'ubuntusso' - - def _add_account(self, account): - row = self._create_row(account) - self.listbox_accounts.prepend(row) - self._check_ui() + account_iter = self._get_account_iter(account) + if account_iter is not None: + return + + account_iter = self.liststore_account.append() + self.liststore_account.set(account_iter, + [Column.ID, Column.MAIL, Column.ACCOUNT], + [account.props.id, account.props.presentation_identity, account]) + self._check_ui(select) def _remove_account(self, account): - for row in self.listbox_accounts.get_children(): - if row.account == account: - row.destroy() - self._check_ui() - break + if self._ignore_account(account): + return + + account_iter = self._get_account_iter(account) + if account_iter is None: + return + + self.liststore_account.remove(account_iter) + self._check_ui(select=False) + + def _response_if_valid(self, account): + def cb(source, res, data): + try: + source.call_ensure_credentials_finish(res) + valid = True + except GLib.Error as e: + logging.warning("call_ensure_credentials_finish exception: %s", + e.message) + valid = False + + if not valid: + try: + self._spawn_goa_with_args(account.props.id, None) + except GLib.Error as e: + logging.warning ('Failed to spawn gnome-control-center: %s', + e.message) + else: + self.account = account + self.dialog.response(Gtk.ResponseType.OK) + + account.call_ensure_credentials(None, cb, None) def _build_dbus_params(self, action, arg): builder = GLib.VariantBuilder.new(GLib.VariantType.new('av')) @@ -118,7 +183,8 @@ v = GLib.Variant.new_variant(s) builder.add_value(v) - array = GLib.Variant.new_tuple(GLib.Variant.new_string('online-accounts'), builder.end()) + array = GLib.Variant.new_tuple( + GLib.Variant.new_string('online-accounts'), builder.end()) array = GLib.Variant.new_variant(array) param = GLib.Variant.new_tuple( @@ -135,94 +201,43 @@ 'org.gtk.Actions', None) param = self._build_dbus_params(action, arg) - timeout = 10*60*1000 # 10 minutes should be enough to create an account - proxy.call_sync('Activate', param, Gio.DBusCallFlags.NONE, timeout, None) - - def _create_row(self, account): - identity = account.props.presentation_identity - provider_name = account.props.provider_name - - row = Gtk.ListBoxRow.new() - row.show() - - hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6) - hbox.set_hexpand(True) - hbox.show() - - image = Gtk.Image.new_from_icon_name('avatar-default', Gtk.IconSize.DIALOG) - image.set_pixel_size(48) - image.show() - hbox.pack_start(image, False, False, 0) - - vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 2) - vbox.set_valign(Gtk.Align.CENTER) - vbox.show() - hbox.pack_start(vbox, False, False, 0) - - if identity: - ilabel = Gtk.Label.new() - ilabel.set_halign(Gtk.Align.START) - ilabel.set_markup('{}'.format(identity)) - ilabel.show() - vbox.pack_start(ilabel, True, True, 0) - - if provider_name: - plabel = Gtk.Label.new() - plabel.set_halign(Gtk.Align.START) - plabel.set_markup('{}'.format(provider_name)) - plabel.show() - vbox.pack_start(plabel, True, True, 0) - - warning_icon = Gtk.Image.new_from_icon_name('dialog-warning-symbolic', Gtk.IconSize.BUTTON) - warning_icon.set_no_show_all(True) - warning_icon.set_margin_end (15) - hbox.pack_end(warning_icon, False, False, 0) - - row.add(hbox) - - account.bind_property('attention-needed', warning_icon, 'visible', - GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE) - - row.account = account - return row - - # Signals handlers - def _listbox_accounts_row_activated_cb(self, listbox, row): - account = row.account - - if account is None: - # TODO (azzar1): there is no easy way to put this to false - # if the user close the windows without adding an account. - # We need to discuss with goa's upstream to support such usercases - try: - self._spawn_goa_with_args('add', 'ubuntusso') - self.dispose_on_new_account = True - except GLib.Error as e: - logging.warning ('Failed to spawing gnome-control-center: %s', e.message) - else: - if account.props.attention_needed: - try: - self._spawn_goa_with_args(account.props.id, None) - except GLib.Error as e: - logging.warning ('Failed to spawing gnome-control-center: %s', e.message) - else: - self.account = account - self.dialog.response(Gtk.ResponseType.OK) + proxy.call_sync('Activate', param, Gio.DBusCallFlags.NONE, -1, None) def _account_added_cb(self, goa_client, goa_object): account = goa_object.get_account() - if not self._is_account_supported(account): + if self._ignore_account(account): return if not self.dispose_on_new_account: - self._add_account(account) + self._add_account(account, True) else: - self.account = account - self.dialog.response(Gtk.ResponseType.OK) + self._response_if_valid(account) def _account_removed_cb(self, goa_client, goa_object): account = goa_object.get_account() - if self._is_account_supported(account): + if not self._ignore_account(account): self._remove_account(account) + def _button_add_another_clicked_cb(self, button): + try: + # There is no easy way to put this to false if the user close the + # windows without adding an account. + self._spawn_goa_with_args('add', 'ubuntusso') + self.dispose_on_new_account = True + except GLib.Error as e: + logging.warning ('Failed to spawn control-center: %s', e.message) + + def _button_cancel_clicked_cb(self, button): + self.dialog.response(Gtk.ResponseType.CANCEL) + + def _button_continue_clicked_cb(self, button): + naccounts = len(self.liststore_account) + + account = None + if naccounts >= 1: + active_index = self.combobox_account.get_active() + account = self.liststore_account[active_index][Column.ACCOUNT] - + if account is None: + self._button_add_another_clicked_cb(self.button_add_another) + else: + self._response_if_valid(account) diff -Nru software-properties-0.96.24.32.1/softwareproperties/gtk/DialogLivepatchError.py software-properties-0.96.24.32.14/softwareproperties/gtk/DialogLivepatchError.py --- software-properties-0.96.24.32.1/softwareproperties/gtk/DialogLivepatchError.py 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/gtk/DialogLivepatchError.py 2019-07-12 13:59:10.000000000 +0000 @@ -1,5 +1,5 @@ # -# Copyright (c) 2017-2018 Canonical +# Copyright (c) 2017-2019 Canonical # # Authors: # Andrea Azzarone @@ -21,6 +21,8 @@ import os +from gettext import gettext as _ + from softwareproperties.gtk.utils import ( setup_ui, ) @@ -31,20 +33,27 @@ RESPONSE_SETTINGS = 100 RESPONSE_IGNORE = 101 + primary = _("Sorry, there's been a problem with setting up Canonical Livepatch.") + def __init__(self, parent, datadir): """setup up the gtk dialog""" self.parent = parent - setup_ui(self, os.path.join(datadir, "gtkbuilder", - "dialog-livepatch-error.ui"), domain="software-properties") + setup_ui( + self, + os.path.join(datadir, "gtkbuilder", "dialog-livepatch-error.ui"), + domain="software-properties") self.dialog = self.messagedialog_livepatch - self.dialog.use_header_bar = True self.dialog.set_transient_for(parent) def run(self, error, show_settings_button): - self.dialog.format_secondary_markup( - "The error was: \"%s\"" % error.strip()) + p = "{}".format(self.primary) + self.label_primary.set_markup(p) + + textbuffer = self.treeview_message.get_buffer() + textbuffer.set_text(error) + self.button_settings.set_visible(show_settings_button) res = self.dialog.run() self.dialog.hide() diff -Nru software-properties-0.96.24.32.1/softwareproperties/gtk/LivepatchPage.py software-properties-0.96.24.32.14/softwareproperties/gtk/LivepatchPage.py --- software-properties-0.96.24.32.1/softwareproperties/gtk/LivepatchPage.py 1970-01-01 00:00:00.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/gtk/LivepatchPage.py 2019-07-12 13:59:10.000000000 +0000 @@ -0,0 +1,421 @@ +# +# Copyright (c) 2019 Canonical +# +# Authors: +# Andrea Azzarone +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +import datetime +import gettext +from gettext import gettext as _ +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gio, GLib, GObject, Gtk +import logging + +from softwareproperties.GoaAuth import GoaAuth +from softwareproperties.LivepatchService import ( + LivepatchService, + LivepatchAvailability) +from .DialogAuth import DialogAuth +from .DialogLivepatchError import DialogLivepatchError + + +class LivepatchPage(object): + + # Constants + COMMON_ISSUE_URL = 'https://wiki.ubuntu.com/Kernel/Livepatch#CommonIssues' + GENERIC_ERR_MSG = _('Canonical Livepatch has experienced an internal error.' + ' Please refer to {} for further information.'.format(COMMON_ISSUE_URL)) + + def __init__(self, parent): + self._parent = parent + + self._timeout_handler = -1 + self._waiting_livepatch_response = False + + self._lps = LivepatchService() + self._auth = GoaAuth() + + self._un_settings = None + source = Gio.SettingsSchemaSource.get_default() + if source is not None: + schema = source.lookup('com.ubuntu.update-notifier', True) + if schema is not None: + settings = Gio.Settings.new('com.ubuntu.update-notifier') + if schema.has_key('show-livepatch-status-icon'): + self._un_settings = settings + + # Connect signals + self._lps.connect( + 'notify::availability', self._lps_availability_changed_cb) + self._lps.connect( + 'notify::enabled', self._lps_enabled_changed_cb) + self._auth.connect( + 'notify', self._auth_changed_cb) + self._state_set_handler = self._parent.switch_livepatch.connect( + 'state-set', self._switch_state_set_cb) + self._parent.button_livepatch_login.connect( + 'clicked', self._button_livepatch_login_clicked_cb) + self._parent.checkbutton_livepatch_topbar.connect( + 'toggled', self._checkbutton_livepatch_topbar_toggled_cb) + + if self._un_settings is not None: + self._un_settings_handler = self._un_settings.connect( + 'changed::show-livepatch-status-icon', self._un_settings_changed_cb) + + self._lps.trigger_availability_check() + + @property + def waiting_livepatch_response(self): + return self._waiting_livepatch_response + + # Private methods + def _trigger_ui_update(self, skip=False, error_message=None): + """Trigger the update of every single user interface component according + to the current state. + + Args: + skip (bool): whether to trigger the update after a small timeout. + Defaults to False. + error_message (str): error message to display. Defaults to None. + """ + def do_ui_update(): + self._timeout_handler = -1 + + self._update_switch() + self._update_spinner() + self._update_switch_label() + self._update_auth_button() + self._update_stack(error_message) + self._update_topbar_checkbutton() + + return False + + if self._timeout_handler > 0: + GObject.source_remove(self._timeout_handler) + self._timeout_handler = -1 + + if skip: + do_ui_update() + else: + self._timeout_handler = GLib.timeout_add_seconds(2, do_ui_update) + + def _update_switch(self): + """Update the state of the on/off switch.""" + switch = self._parent.switch_livepatch + + availability = self._lps.props.availability + enabled = self._lps.props.enabled + logged = self._auth.logged + + switch.set_sensitive( + availability == LivepatchAvailability.TRUE and + (enabled or logged)) + + if self._waiting_livepatch_response: + return + + self._parent.switch_livepatch.handler_block(self._state_set_handler) + switch.set_state(switch.get_sensitive() and enabled) + self._parent.switch_livepatch.handler_unblock(self._state_set_handler) + + def _update_spinner(self): + """Update the state of the in-progress spinner.""" + spinner = self._parent.spinner_livepatch + availability = self._lps.props.availability + + spinner.set_visible(availability == LivepatchAvailability.CHECKING) + spinner.props.active = (availability == LivepatchAvailability.CHECKING) + + def _update_switch_label(self): + """Update the text of the label next to the on/off switch.""" + availability = self._lps.props.availability + logged = self._auth.logged + + if availability == LivepatchAvailability.CHECKING: + msg = _('Checking availability…') + elif availability == LivepatchAvailability.NO_CONNECTIVITY: + msg = _('Livepatch requires an Internet connection.') + elif availability == LivepatchAvailability.FALSE: + msg = _('Livepatch is not available for this system.') + else: + if self._parent.switch_livepatch.get_active(): + msg = _("Livepatch is on.") + elif not logged: + msg = _("To use Livepatch you need to sign in.") + else: + msg = _("Livepatch is off.") + + self._parent.label_livepatch_switch.set_label(msg) + + def _update_auth_button(self): + """Update the state and the label of the authentication button.""" + button = self._parent.button_livepatch_login + + availability = self._lps.props.availability + logged = self._auth.logged + + button.set_visible( + availability == LivepatchAvailability.TRUE and + not self._parent.switch_livepatch.get_active()) + button.set_label(_('Sign Out') if logged else _('Sign In…')) + + def _update_stack(self, error_message): + """Update the state of the stack. + + If livepatch is not available nothing will be shown, if an error + occurred an error message will be shown in a text view, otherwise the + current livepatch status (e.g. a list of CVE fixes) will be shown. + + Args: + error_message (str): error message to display. + """ + availability = self._lps.props.availability + availability_message = self._lps.props.availability_message + + has_error = ( + error_message is not None or + (availability == LivepatchAvailability.FALSE and + availability_message is not None)) + + if has_error: + self._parent.stack_livepatch.set_visible_child_name('page_livepatch_message') + self._parent.stack_livepatch.set_visible(True) + text_buffer = self._parent.textview_livepatch.get_buffer() + text_buffer.delete( + text_buffer.get_start_iter(), text_buffer.get_end_iter()) + text_buffer.insert_markup( + text_buffer.get_end_iter(), + error_message or availability_message, -1) + return + + if availability == LivepatchAvailability.CHECKING or not self._parent.switch_livepatch.get_active(): + self._parent.stack_livepatch.set_visible(False) + else: + self._update_status() + + def _update_topbar_checkbutton(self): + """Update the state of the checkbutton to show/hide Livepatch status in + the top bar. + """ + visibile = self._un_settings is not None + self._parent.checkbutton_livepatch_topbar.set_visible(visibile) + + if visibile: + availability = self._lps.props.availability + + self._parent.checkbutton_livepatch_topbar.set_sensitive( + availability == LivepatchAvailability.TRUE and + self._lps.props.enabled) + self._parent.checkbutton_livepatch_topbar.set_active( + availability == LivepatchAvailability.TRUE and + self._lps.props.enabled and + self._un_settings.get_boolean('show-livepatch-status-icon')) + + def _format_timedelta(self, td): + days = td.days + hours = td.seconds // 3600 + minutes = td.seconds // 60 + + if days > 0: + return gettext.ngettext( + '({} day ago)', + '({} days ago)', + days).format(days) + elif hours > 0: + return gettext.ngettext( + '({} hour ago)', + '({} hours ago)', + hours).format(hours) + elif minutes > 0: + return gettext.ngettext( + '({} minute ago)', + '({} minutes ago)', + minutes).format(minutes) + else: + return '' + + def _datetime_to_str(self, dt): + gdt = GLib.DateTime.new_from_unix_utc(dt.timestamp()) + td = datetime.datetime.now(dt.tzinfo) - dt + return '{} {}'.format( + gdt.to_local().format('%x %H:%M'), + self._format_timedelta(td)) + + def _update_status(self): + """Populate the UI to reflect the Livepatch status""" + status = self._lps.get_status() + + if status is None: + if not self._waiting_livepatch_response: + self._trigger_ui_update(skip=True, error_message=_('Failed to retrieve Livepatch status.')) + return + + self._parent.stack_livepatch.set_visible_child_name('page_livepatch_status') + self._parent.stack_livepatch.set_visible(True) + + check_state = status['Status'][0]['Livepatch']['CheckState'] if status else None + state = status['Status'][0]['Livepatch']['State'] if status else None + + if check_state == 'check-failed': + self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG) + return + + if state in ['applied-with-bug', 'apply-failed', 'unknown']: + self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG) + return + + self._parent.label_livepatch_last_update.set_label( + _("Last check for updates: {}").format( + self._datetime_to_str(status['Last-Check']) if status else _('None yet'))) + + if state in ['unapplied', 'nothing-to-apply'] or state == None: + self._parent.label_livepatch_header.set_label(_('No updates currently applied.')) + self._parent.scrolledwindow_livepatch_fixes.set_visible(False) + elif state == 'applied': + self._parent.label_livepatch_header.set_label(_('Updates currently applied:')) + self._parent.scrolledwindow_livepatch_fixes.set_visible(True) + self._update_fixes(status['Status'][0]['Livepatch']['Fixes']) + else: + logging.warning('Livepatch status contains an invalid state: {}'.format(state)) + + if check_state == 'needs-check' or state == 'unapplied' or state == 'applying': + self._trigger_ui_update() + + def _update_fixes(self, fixes): + """Populate the UI to show the list of applied CVE fixes.""" + treeview = self._parent.treeview_livepatch + liststore = treeview.get_model() + liststore.clear() + + for fix in fixes: + fix_iter = liststore.append() + liststore.set(fix_iter, [0], [self._format_fix(fix)]) + + def _format_fix(self, fix): + """Format a fix in a UI friendly text.""" + return '{}\n{}'.format( + fix['Name'], fix['Description'].replace('\n', ' ')) + + def _do_login(self): + """Start the authentication flow to retrieve the livepatch token.""" + dialog = DialogAuth(self._parent.window_main, self._parent.datadir) + + if dialog.run() == Gtk.ResponseType.OK: + self._auth.login(dialog.account) + self._parent.switch_livepatch.set_state(True) + + def _do_logout(self): + """Start the de-authentication flow.""" + self._auth.logout() + + # Signals handler + def _lps_availability_changed_cb(self, o, v): + self._trigger_ui_update(skip=True) + + def _lps_enabled_changed_cb(self, o, v): + if self._waiting_livepatch_response: + return + self._trigger_ui_update(skip=False) + + def _auth_changed_cb(self, o, v): + self._trigger_ui_update(skip=True) + + def _switch_state_set_cb(self, widget, state): + if not self._waiting_livepatch_response: + self._waiting_livepatch_response = True + + token = self._auth.token or '' + self._parent.backend.SetLivepatchEnabled( + state, token, + reply_handler=self._enabled_reply_handler, + error_handler=self._enabled_error_handler, + timeout=1200) + + self._trigger_ui_update(skip=True) + self._parent.switch_livepatch.set_state(state) + + return False + + def _button_livepatch_login_clicked_cb(self, button): + if self._auth.logged: + self._do_logout() + else: + self._do_login() + + def _checkbutton_livepatch_topbar_toggled_cb(self, button): + if not button.get_sensitive(): + return + self._un_settings.handler_block(self._un_settings_handler) + self._un_settings.set_boolean('show-livepatch-status-icon', button.get_active()) + self._un_settings.handler_unblock(self._un_settings_handler) + + + def _un_settings_changed_cb(self, settings, key): + self._trigger_ui_update(skip=True) + + def _show_error_dialog(self, message): + dialog = DialogLivepatchError( + self._parent.window_main, + self._parent.datadir) + + response = dialog.run( + error=message, + show_settings_button=not self._parent.window_main.is_visible()) + + if response == DialogLivepatchError.RESPONSE_SETTINGS: + self._parent.window_main.show() + self._parent.notebook_main.set_current_page(6) + elif not self._parent.window_main.is_visible(): + self._parent.on_close_button(None) + + # DBus replay handlers + def _enabled_reply_handler(self, is_error, prompt): + if self._parent.switch_livepatch.get_active() == self._lps.props.enabled: + self._waiting_livepatch_response = False + self._trigger_ui_update(skip=True) + + if not self._parent.window_main.is_visible(): + self._parent.on_close_button(None) + else: + if is_error: + self._waiting_livepatch_response = False + self._trigger_ui_update(skip=True) + self._show_error_dialog(prompt) + else: + # The user tooggled on/off the switch while we were waiting + # livepatch to respond back. + token = self._auth.token or '' + self._parent.backend.SetLivepatchEnabled( + self._parent.switch_livepatch.get_active(), + token, + reply_handler=self._enabled_reply_handler, + error_handler=self._enabled_error_handler, + timeout=1200) + + def _enabled_error_handler(self, e): + self._waiting_livepatch_response = False + self._trigger_ui_update(skip=True) + + if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy': + logging.warning("Authentication canceled, changes have not been saved") + + if not self._parent.window_main.is_visible(): + self._parent.on_close_button(None) + else: + self._show_error_dialog(str(e)) diff -Nru software-properties-0.96.24.32.1/softwareproperties/gtk/SimpleGtkbuilderApp.py software-properties-0.96.24.32.14/softwareproperties/gtk/SimpleGtkbuilderApp.py --- software-properties-0.96.24.32.1/softwareproperties/gtk/SimpleGtkbuilderApp.py 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/gtk/SimpleGtkbuilderApp.py 2019-07-12 13:59:10.000000000 +0000 @@ -38,6 +38,9 @@ def on_activate(self, data=None): self.add_window(self.window_main) + if not self.window_main.is_visible(): + self.window_main.show() + def run(self): """ Starts the main loop of processing events checking for Control-C. diff -Nru software-properties-0.96.24.32.1/softwareproperties/gtk/SoftwarePropertiesGtk.py software-properties-0.96.24.32.14/softwareproperties/gtk/SoftwarePropertiesGtk.py --- software-properties-0.96.24.32.1/softwareproperties/gtk/SoftwarePropertiesGtk.py 2018-04-17 16:20:41.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/gtk/SoftwarePropertiesGtk.py 2020-05-08 14:47:19.000000000 +0000 @@ -27,9 +27,6 @@ import apt import apt_pkg -import aptsources.distro -from datetime import datetime -import distro_info import dbus from gettext import gettext as _ import gettext @@ -40,6 +37,7 @@ import logging import threading import sys +import time import gi gi.require_version("Gdk", "3.0") @@ -52,11 +50,9 @@ from .DialogEdit import DialogEdit from .DialogCacheOutdated import DialogCacheOutdated from .DialogAddSourcesList import DialogAddSourcesList -from .DialogLivepatchError import DialogLivepatchError -from .DialogAuth import DialogAuth +from .LivepatchPage import LivepatchPage import softwareproperties -from softwareproperties.GoaAuth import GoaAuth import softwareproperties.distro from softwareproperties.SoftwareProperties import SoftwareProperties import softwareproperties.SoftwareProperties @@ -85,8 +81,6 @@ STORE_VISIBLE ) = list(range(5)) -LIVEPATCH_TIMEOUT = 1200 - def error(parent_window, summary, msg): """ show a error dialog """ @@ -101,6 +95,18 @@ return False +def get_dependencies(apt_cache, package_name, pattern=None): + """ Get the package dependencies, which can be filtered out by a pattern """ + dependencies = [] + for or_group in apt_cache[package_name].candidate.dependencies: + for dep in or_group: + if dep.rawtype in ["Depends", "PreDepends"]: + dependencies.append(dep.name) + if pattern: + dependencies = [ x for x in dependencies if x.find(pattern) != -1 ] + return dependencies + + class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp): def __init__(self, datadir=None, options=None, file=None, parent=None): @@ -736,6 +742,13 @@ def on_isv_source_toggled(self, cell_toggle, path, store): """Enable or disable the selected channel""" + + #FIXME Gtk is doing something that takes a lock while processing + # the events, the polkit auth dialog can't be displayed in return, + # we don't have a proper fix and it might require GTK changes, + # meanwhile we want a working interface so workaround with a sleep + # https://launchpad.net/bugs/1727908 + time.sleep(0.3) #FIXME cdroms need to disable the comps in the childs and sources iter = store.get_iter((int(path),)) source_entry = store.get_value(iter, STORE_SOURCE) @@ -1033,7 +1046,9 @@ d = DialogCacheOutdated(self.window_main, self.datadir) d.run() - if self.waiting_livepatch_response: + + self.quit_when_livepatch_responds = False + if self.livepatch_page.waiting_livepatch_response: self.quit_when_livepatch_responds = True self.hide() else: @@ -1090,6 +1105,14 @@ for pkg in self.driver_changes: if pkg.is_installed: removals.append(pkg.shortname) + # The main NVIDIA package is only a metapackage. + # We need to collect its dependencies, so that + # we can uninstall the driver properly. + if 'nvidia' in pkg.shortname: + for dep in get_dependencies(self.apt_cache, pkg.shortname, 'nvidia'): + dep_pkg = self.apt_cache[dep] + if dep_pkg.is_installed: + removals.append(dep_pkg.shortname) else: installs.append(pkg.shortname) @@ -1234,7 +1257,7 @@ lmp = self.apt_cache[linux_meta] if not lmp.is_installed: self.driver_changes.append(lmp) - except KeyError: + except (AttributeError, KeyError): pass if button.get_active(): @@ -1463,144 +1486,5 @@ else: self.label_driver_action.set_label(_("No proprietary drivers are in use.")) - # - # Livepatch - # def init_livepatch(self): - self.goa_auth = GoaAuth() - self.waiting_livepatch_response = False - self.quit_when_livepatch_responds = False - - if not self.is_livepatch_supported(): - self.grid_livepatch.set_visible(False) - return - - self.checkbutton_livepatch.set_active(self.is_livepatch_enabled()) - self.on_goa_auth_changed() - - # hacky way to monitor if livepatch is enabled or not - file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE) - self.lp_monitor = file.monitor_file(Gio.FileMonitorFlags.NONE) - - # connect to signals - self.handlers[self.goa_auth] = \ - self.goa_auth.connect('notify', lambda o, p: self.on_goa_auth_changed()) - self.handlers[self.checkbutton_livepatch] = \ - self.checkbutton_livepatch.connect('toggled', self.on_checkbutton_livepatch_toggled) - self.handlers[self.button_ubuntuone] = \ - self.button_ubuntuone.connect('clicked', self.on_button_ubuntuone_clicked) - self.handlers[self.lp_monitor] = \ - self.lp_monitor.connect('changed', self.on_livepatch_status_changed) - - def is_livepatch_supported(self): - distro = aptsources.distro.get_distro() - di = distro_info.UbuntuDistroInfo() - return di.is_lts(distro.codename) and distro.codename in di.supported(datetime.now().date()) - - def on_goa_auth_changed(self): - if self.goa_auth.logged: - self.button_ubuntuone.set_label(_('Sign Out')) - - if self.goa_auth.token: - self.checkbutton_livepatch.set_sensitive(True) - self.label_livepatch_login.set_label(_('Signed in as %s' % self.goa_auth.username)) - else: - self.checkbutton_livepatch.set_sensitive(False) - text = _('%s isn\'t authorized to use Livepatch.' % self.goa_auth.username) - text = "" + text + "" - self.label_livepatch_login.set_markup(text) - else: - self.checkbutton_livepatch.set_sensitive(False) - self.button_ubuntuone.set_label(_('Sign In…')) - self.label_livepatch_login.set_label(_('To use Livepatch you need to sign in.')) - - def on_livepatch_status_changed(self, file_monitor, file, other_file, event_type): - if not self.waiting_livepatch_response: - self.checkbutton_livepatch.set_active(self.is_livepatch_enabled()) - - def on_button_ubuntuone_clicked(self, button): - if self.goa_auth.logged: - self.do_logout() - else: - self.do_login() - - def do_login(self): - try: - # Show login dialog! - dialog = DialogAuth(self.window_main, self.datadir) - response = dialog.run() - except Exception as e: - logging.error(e) - error(self.window_main, - _("Error enabling Canonical Livepatch"), - _("Please check your Internet connection.")) - else: - if response == Gtk.ResponseType.OK: - self.goa_auth.login(dialog.account) - if self.goa_auth.logged: - self.checkbutton_livepatch.set_active(True) - - def do_logout(self): - self.goa_auth.logout() - self.checkbutton_livepatch.set_active(False) - - def on_checkbutton_livepatch_toggled(self, checkbutton): - if self.waiting_livepatch_response: - return - - self.waiting_livepatch_response = True - - token = '' - enabled = False - if self.checkbutton_livepatch.get_active(): - enabled = True - token = self.goa_auth.token if self.goa_auth.token else '' - self.backend.SetLivepatchEnabled(enabled, token, - reply_handler=self.livepatch_enabled_reply_handler, - error_handler=self.livepatch_enabled_error_handler, - timeout=LIVEPATCH_TIMEOUT) - - def livepatch_enabled_reply_handler(self, is_error, prompt): - self.sync_checkbutton_livepatch(is_error, prompt) - - def livepatch_enabled_error_handler(self, e): - if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy': - logging.error("Authentication canceled, changes have not been saved") - self.sync_checkbutton_livepatch(is_error=True, prompt=None) - else: - self.sync_checkbutton_livepatch(is_error=True, prompt=str(e)) - - def sync_checkbutton_livepatch(self, is_error, prompt): - if is_error: - self.waiting_livepatch_response = False - self.checkbutton_livepatch.handler_block(self.handlers[self.checkbutton_livepatch]) - self.checkbutton_livepatch.set_active(self.is_livepatch_enabled()) - self.checkbutton_livepatch.handler_unblock(self.handlers[self.checkbutton_livepatch]) - - if prompt: - dialog = DialogLivepatchError(self.window_main, self.datadir) - response = dialog.run(prompt, show_settings_button=self.quit_when_livepatch_responds) - if response == DialogLivepatchError.RESPONSE_SETTINGS: - self.window_main.show() - self.quit_when_livepatch_responds = False - else: - do_dbus_call = False - if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active(): - do_dbus_call = True - enabled = False - token = '' - elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active(): - do_dbus_call = True - enabled = True - token = self.goa_auth.token if self.goa_auth.token else '' - else: - self.waiting_livepatch_response = False - - if do_dbus_call: - self.backend.SetLivepatchEnabled(enabled, token, - reply_handler=self.livepatch_enabled_reply_handler, - error_handler=self.livepatch_enabled_error_handler, - timeout=LIVEPATCH_TIMEOUT) - - if self.quit_when_livepatch_responds: - self.on_close_button(self.button_close) + self.livepatch_page = LivepatchPage(self) diff -Nru software-properties-0.96.24.32.1/softwareproperties/gtk/utils.py software-properties-0.96.24.32.14/softwareproperties/gtk/utils.py --- software-properties-0.96.24.32.1/softwareproperties/gtk/utils.py 2018-04-04 07:29:37.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/gtk/utils.py 2019-07-12 13:59:10.000000000 +0000 @@ -18,13 +18,19 @@ from __future__ import print_function +import aptsources.distro +from datetime import datetime +import distro_info +from functools import wraps import gi gi.require_version("Gtk", "3.0") -from gi.repository import Gtk +from gi.repository import Gio, Gtk import logging LOG=logging.getLogger(__name__) +import time + def setup_ui(self, path, domain): # setup ui self.builder = Gtk.Builder() @@ -37,3 +43,52 @@ setattr(self, name, o) else: logging.debug("can not get name for object '%s'" % o) + +def has_gnome_online_accounts(): + try: + d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop') + return d != None + except Exception: + return False + +def is_current_distro_lts(): + distro = aptsources.distro.get_distro() + di = distro_info.UbuntuDistroInfo() + return di.is_lts(distro.codename) + +def is_current_distro_supported(): + distro = aptsources.distro.get_distro() + di = distro_info.UbuntuDistroInfo() + return distro.codename in di.supported(datetime.now().date()) + +def retry(exceptions, tries=10, delay=0.1, backoff=2): + """ + Retry calling the decorated function using an exponential backoff. + + Args: + exceptions: The exception to check. may be a tuple of + exceptions to check. + tries: Number of times to try (not retry) before giving up. + delay: Initial delay between retries in seconds. + backoff: Backoff multiplier (e.g. value of 2 will double the delay + each retry). + """ + def deco_retry(f): + + @wraps(f) + def f_retry(*args, **kwargs): + mtries, mdelay = tries, delay + while mtries > 1: + try: + return f(*args, **kwargs) + except exceptions as e: + msg = '{}, Retrying in {} seconds...'.format(e, mdelay) + logging.warning(msg) + time.sleep(mdelay) + mtries -= 1 + mdelay *= backoff + return f(*args, **kwargs) + + return f_retry # true decorator + + return deco_retry diff -Nru software-properties-0.96.24.32.1/softwareproperties/LivepatchService.py software-properties-0.96.24.32.14/softwareproperties/LivepatchService.py --- software-properties-0.96.24.32.1/softwareproperties/LivepatchService.py 1970-01-01 00:00:00.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/LivepatchService.py 2019-07-12 13:59:10.000000000 +0000 @@ -0,0 +1,254 @@ +# +# Copyright (c) 2019 Canonical +# +# Authors: +# Andrea Azzarone +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +from gettext import gettext as _ +import logging + +import gi +from gi.repository import Gio, GLib, GObject + +try: + import dateutil.parser + import requests_unixsocket + + gi.require_version('Snapd', '1') + from gi.repository import Snapd +except(ImportError, ValueError): + pass + +from softwareproperties.gtk.utils import ( + has_gnome_online_accounts, + is_current_distro_lts, + is_current_distro_supported, + retry +) + +from softwareproperties.LivepatchSnap import LivepatchSnap + + +def datetime_parser(json_dict): + for (key, value) in json_dict.items(): + try: + json_dict[key] = dateutil.parser.parse(value) + except (ValueError, TypeError): + pass + return json_dict + +class LivepatchAvailability: + FALSE = 0 + TRUE = 1 + NO_CONNECTIVITY=3 + CHECKING = 2 + + +class LivepatchService(GObject.GObject): + + # Constants + STATUS_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd.sock/status' + ENABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/enable' + DISABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable' + LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token' + + ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}') + DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}') + + # GObject.GObject + __gproperties__ = { + 'availability': ( + int, None, None, + LivepatchAvailability.FALSE, + LivepatchAvailability.CHECKING, + LivepatchAvailability.FALSE, + GObject.PARAM_READABLE), + 'availability-message': ( + str, None, None, None, GObject.PARAM_READABLE), + 'enabled': ( + bool, None, None, False, GObject.PARAM_READABLE), + } + + def __init__(self): + GObject.GObject.__init__(self) + + self._timeout_id = 0 + + self._snap = LivepatchSnap() + self._session = requests_unixsocket.Session() + + # Init Properties + self._availability = LivepatchAvailability.FALSE + self._availability_message = None + lp_file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE) + self._enabled = lp_file.query_exists() + + # Monitor connectivity status + self._nm = Gio.NetworkMonitor.get_default() + self._nm.connect('notify::connectivity', self._network_changed_cb) + + # Monitor status of canonical-livepatch + self._lp_monitor = lp_file.monitor_file(Gio.FileMonitorFlags.NONE) + self._lp_monitor.connect('changed', self._livepatch_enabled_changed_cb) + + def do_get_property(self, pspec): + if pspec.name == 'availability': + return self._availability + elif pspec.name == 'availability-message': + return self._availability_message + elif pspec.name == 'enabled': + return self._enabled + else: + raise AssertionError + + # Public API + def trigger_availability_check(self): + """Trigger a Livepatch availability check to be executed after a short + timeout. Multiple triggers will result in a single request. + + A notify::availability will be emitted when the check starts, and + another one when the check ends. + """ + def _update_availability(): + # each rule is a tuple of two elements, a callable and a string. The + # string rapresents the error message that needs to be shown if the + # callable returns false. + rules = [ + (lambda: self._snap.get_status() != Snapd.SnapStatus.UNKNOWN, + _('Canonical Livepatch snap is not available.')), + (has_gnome_online_accounts, + _('Gnome Online Accounts is required to enable Livepatch.')), + (is_current_distro_lts, + _('Livepatch is not available for this release.')), + (is_current_distro_supported, + _('The current release is no longer supported.'))] + + if self._nm.props.connectivity != Gio.NetworkConnectivity.FULL: + self._availability = LivepatchAvailability.NO_CONNECTIVITY + self._availability_message = None + else: + for func, message in rules: + if not func(): + self._availability = LivepatchAvailability.FALSE + self._availability_message = message + break + else: + self._availability = LivepatchAvailability.TRUE + self._availability_message = None + + self.notify('availability') + self.notify('availability-message') + + self._timeout_id = 0 + return False + + self._availability = LivepatchAvailability.CHECKING + self._availability_message = None + self.notify('availability') + self.notify('availability-message') + + if self._timeout_id == 0: + self._timeout_id = GLib.timeout_add_seconds(3, _update_availability) + + def set_enabled(self, enabled, token): + """Enable or disable Canonical Livepatch in the current system. This + function will return once the operation succeeded or failed. + + Args: + enabled(bool): wheater to enable or disable the service. + token(str): the authentication token to be used to enable Canonical + Livepatch service. + + Returns: + (False, '') if successful, (True, error_message) otherwise. + """ + if self._enabled == enabled: + return False, '' + + if not enabled: + return self._disable_service() + elif self._snap.get_status() == Snapd.SnapStatus.ACTIVE: + return self._enable_service(token) + else: + success, msg = self._snap.enable_or_install() + return self._enable_service(token) if success else (True, msg) + + def get_status(self): + """Synchronously retrieve the status of Canonical Livepatch. + + Returns: + str: The status. A valid string for success, None otherwise. + """ + try: + params = {'verbosity': 3, 'format': 'json'} + r = self._session.get(self.STATUS_ENDPOINT, params=params) + return r.json(object_hook=datetime_parser) + except Exception as e: + logging.debug('Failed to get Livepatch status: {}'.format(str(e))) + return None + + # Private methods + def _enable_service(self, token): + """Enable Canonical Livepatch in the current system. This function will + return once the operation succeeded or failed. + + Args: + token(str): the authentication token to be used to enable Canonical + Livepatch service. + + Returns: + (False, '') if successful, (True, error_message) otherwise. + """ + try: + return self._enable_service_with_retry(token) + except Exception as e: + return True, self.ENABLE_ERROR_MSG.format(str(e)) + + @retry(Exception) + def _enable_service_with_retry(self, token): + params = {'auth-token': token} + r = self._session.put(self.ENABLE_ENDPOINT, params=params) + return not r.ok, '' if r.ok else self.ENABLE_ERROR_MSG.format(r.text) + + def _disable_service(self): + """Disable Canonical Livepatch in the current system. This function will + return once the operation succeeded or failed. + + Returns: + (False, '') if successful, (True, error_message) otherwise. + """ + try: + return self._disable_service_with_retry() + except Exception as e: + return True, self.DISABLE_ERROR_MSG.format(str(e)) + + + @retry(Exception) + def _disable_service_with_retry(self): + r = self._session.put(self.DISABLE_ENDPOINT) + return not r.ok, '' if r.ok else self.DISABLE_ERROR_MSG.format(r.text) + + # Signals handlers + def _network_changed_cb(self, monitor, network_available): + self.trigger_availability_check() + + def _livepatch_enabled_changed_cb(self, fm, file, other_file, event_type): + enabled = file.query_exists() + if self._enabled != enabled: + self._enabled = enabled + self.notify('enabled') diff -Nru software-properties-0.96.24.32.1/softwareproperties/LivepatchSnap.py software-properties-0.96.24.32.14/softwareproperties/LivepatchSnap.py --- software-properties-0.96.24.32.1/softwareproperties/LivepatchSnap.py 1970-01-01 00:00:00.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/LivepatchSnap.py 2019-07-12 13:59:10.000000000 +0000 @@ -0,0 +1,135 @@ +# +# Copyright (c) 2019 Canonical +# +# Authors: +# Andrea Azzarone +# +# 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, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 +# USA + +from gettext import gettext as _ +import logging + +import gi +from gi.repository import Gio, GLib + +try: + gi.require_version('Snapd', '1') + from gi.repository import Snapd +except(ImportError, ValueError): + pass + + +class LivepatchSnap(object): + + # Constants + SNAP_NAME = 'canonical-livepatch' + + # Public API + def __init__(self): + self._snapd_client = Snapd.Client() + self._cancellable = Gio.Cancellable() + + def get_status(self): + """ Get the status of canonical-livepatch snap. + + Returns: + Snapd.SnapStatus.Enun: An enum indicating the status of the snap. + """ + snap = self._get_raw_snap() + return snap.get_status() if snap else Snapd.SnapStatus.UNKNOWN + + def enable_or_install(self): + """Enable or install canonical-livepatch snap. + + Returns: + (True, '') if successful, (False, error_message) otherwise. + """ + status = self.get_status() + + if status == Snapd.SnapStatus.ACTIVE: + logging.warning('{} snap is already active'.format(self.SNAP_NAME)) + return True, '' + elif status == Snapd.SnapStatus.AVAILABLE: + return self._install() + elif status == Snapd.SnapStatus.INSTALLED: + return self._enable() + else: + logging.warning('{} snap is in an unknown state'.format(self.SNAP_NAME)) + return False, _('Canonical Livepatch snap cannot be installed.') + + # Private methods + def _get_raw_snap(self): + """Get the Sanpd.Snap raw object of the canonical-livepatch snapd. + + Returns: + Sanpd.Snap if successful, None otherwise. + """ + try: + snap = self._snapd_client.get_snap_sync( + name=self.SNAP_NAME, + cancellable=self._cancellable) + except GLib.Error as e: + logging.debug('Snapd.Client.get_snap_sync failed: {}'.format(e.message)) + snap = None + + if snap: + return snap + + try: + (snaps, ignored) = self._snapd_client.find_sync( + flags=Snapd.FindFlags.MATCH_NAME, + query=self.SNAP_NAME, + cancellable=self._cancellable) + snap = snaps[0] + except GLib.Error as e: + logging.debug('Snapd.Client.find_sync failed: {}'.format(e.message)) + + return snap + + def _install(self): + """Install canonical-livepatch snap. + + Returns: + (True, '') if successful, (False, error_message) otherwise. + """ + assert self.get_status() == Snapd.SnapStatus.AVAILABLE + + try: + self._snapd_client.install2_sync( + flags=Snapd.InstallFlags.NONE, + name=self.SNAP_NAME, + cancellable=self._cancellable) + except GLib.Error as e: + return False, _('Canonical Livepatch snap cannot be installed: {}'.format(e.message)) + else: + return True, '' + + def _enable(self): + """Enable the canonical-livepatch snap. + + Returns: + (True, '') if successful, (False, error_message) otherwise. + """ + assert self.get_status() == Snapd.SnapStatus.INSTALLED + + try: + self._snapd_client.enable_sync( + name=self.SNAP_NAME, + cancellable=self._cancellable) + except GLib.Error as e: + return False, _('Canonical Livepatch snap cannot be enabled: {}'.format(e.message)) + else: + return True, '' diff -Nru software-properties-0.96.24.32.1/softwareproperties/SoftwareProperties.py software-properties-0.96.24.32.14/softwareproperties/SoftwareProperties.py --- software-properties-0.96.24.32.1/softwareproperties/SoftwareProperties.py 2018-04-17 16:02:47.000000000 +0000 +++ software-properties-0.96.24.32.14/softwareproperties/SoftwareProperties.py 2019-07-12 14:08:41.000000000 +0000 @@ -32,7 +32,6 @@ import os import glob import shutil -import subprocess import threading import atexit import tempfile @@ -65,15 +64,8 @@ from . import ppa from . import cloudarchive -import gi from gi.repository import Gio -try: - gi.require_version('Snapd', '1') - from gi.repository import Snapd -except (ImportError, ValueError): - pass - _SHORTCUT_FACTORIES = [ ppa.shortcut_handler, cloudarchive.shortcut_handler, @@ -100,9 +92,6 @@ RELEASE_UPGRADES_NEVER : 'never', } - # file to monitor canonical-livepatch status - LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token' - def __init__(self, datadir=None, options=None, rootdir="/"): """ Provides the core functionality to configure the used software repositories, the corresponding authentication keys and @@ -874,147 +863,6 @@ except: return False - # - # Livepatch - # - def init_snapd(self): - self.snapd_client = Snapd.Client() - - def get_livepatch_snap_async(self, callback): - assert self.snapd_client - self.snapd_client.list_one_async('canonical-livepatch', - self.cancellable, - self.on_list_one_ready_cb, - callback) - - def on_list_one_ready_cb(self, source_object, result, user_data): - callback = user_data - try: - snap = source_object.list_one_finish(result) - except: - snap = None - if snap: - if callback: - callback(snap) - return - else: - assert self.snapd_client - self.snapd_client.find_async(Snapd.FindFlags.MATCH_NAME, - 'canonical-livepatch', - self.cancellable, - self.on_find_ready_cb, - callback) - - def on_find_ready_cb(self, source_object, result, user_data): - callback = user_data - try: - snaps = source_object.find_finish(result)[0] - except: - snaps = list() - snap = snaps[0] if len(snaps) else None - if callback: - callback(snap) - - def get_livepatch_snap_status(self, snap): - if snap is None: - return Snapd.SnapStatus.UNKNOWN - return snap.get_status() - - # glib-snapd does not keep track of the status of the snap. Use this decorator - # to make it easy to write async functions that will always have an updated - # snap object. - def require_livepatch_snap(func): - def get_livepatch_snap_and_call(*args, **kwargs): - return args[0].get_livepatch_snap_async(lambda snap: func(snap=snap, *args, **kwargs)) - return get_livepatch_snap_and_call - - def is_livepatch_enabled(self): - file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE) - return file.query_exists(None) - - @require_livepatch_snap - def set_livepatch_enabled_async(self, enabled, token, callback, snap=None): - status = self.get_livepatch_snap_status(snap) - if status == Snapd.SnapStatus.UNKNOWN: - if callback: - callback(True, _("Canonical Livepatch snap cannot be installed.")) - elif status == Snapd.SnapStatus.ACTIVE: - if enabled: - error = self.enable_livepatch_service(token) - else: - error = self.disable_livepatch_service() - if callback: - callback(len(error) > 0, error) - elif status == Snapd.SnapStatus.INSTALLED: - if enabled: - self.snapd_client.enable_async(name='canonical-livepatch', - cancellable=self.cancellable, - callback=self.livepatch_enable_snap_cb, - user_data=(callback, token)) - else: - if callback: - callback(False, "") - elif status == Snapd.SnapStatus.AVAILABLE: - if enabled: - self.snapd_client.install_async(name='canonical-livepatch', - cancellable=self.cancellable, - callback=self.livepatch_install_snap_cb, - user_data=(callback, token)) - else: - if callback: - callback(False, "") - - def livepatch_enable_snap_cb(self, source_object, result, user_data): - (callback, token) = user_data - try: - if source_object.enable_finish(result): - error = self.enable_livepatch_service(token) - if callback: - callback(len(error) > 0, error) - except Exception: - if callback: - callback(True, _("Canonical Livepatch snap cannot be enabled.")) - - def livepatch_install_snap_cb(self, source_object, result, user_data): - (callback, token) = user_data - try: - if source_object.install_finish(result): - error = self.enable_livepatch_service(token) - if callback: - callback(len(error) > 0, error) - except Exception: - if callback: - callback(True, _("Canonical Livepatch snap cannot be installed.")) - - def enable_livepatch_service(self, token): - generic_error = _("Canonical Livepatch cannot be enabled.") - - if self.is_livepatch_enabled(): - return "" - - try: - subprocess.check_output(['/snap/bin/canonical-livepatch', 'enable', token], stderr=subprocess.STDOUT) - return "" - except subprocess.CalledProcessError as e: - return e.output if e.output else generic_error - except: - return generic_error - - - def disable_livepatch_service(self): - generic_error = _("Canonical Livepatch cannot be disabled.") - - if not self.is_livepatch_enabled(): - return "" - - try: - subprocess.check_output(['/snap/bin/canonical-livepatch', 'disable'], stderr=subprocess.STDOUT) - return "" - except subprocess.CalledProcessError as e: - return e.output if e.output else generic_error - except: - return generic_error - def shortcut_handler(shortcut): for factory in _SHORTCUT_FACTORIES: ret = factory(shortcut)