[Merge] lp:~azzar1/software-properties/add-canonical-livepatch into lp:software-properties
Brian Murray
brian at ubuntu.com
Fri Mar 16 21:18:26 UTC 2018
I'm not sure I'm the best person to evaluate all the gtk code so I've identified some other things mostly nit-picks but I do think using python3-distro-info would quite a good improvement.
Diff comments:
> === added file 'data/gtkbuilder/dialog-auth.ui'
> --- data/gtkbuilder/dialog-auth.ui 1970-01-01 00:00:00 +0000
> +++ data/gtkbuilder/dialog-auth.ui 2018-03-16 13:02:28 +0000
> @@ -0,0 +1,134 @@
> +<?xml version="1.0" encoding="UTF-8"?>
> +<!-- Generated with glade 3.20.4 -->
> +<interface>
> + <requires lib="gtk+" version="3.10"/>
> + <object class="GtkDialog" id="dialog_auth">
> + <property name="can_focus">False</property>
> + <property name="resizable">False</property>
> + <property name="modal">True</property>
> + <property name="destroy_with_parent">True</property>
> + <property name="type_hint">dialog</property>
> + <property name="deletable">False</property>
> + <child internal-child="vbox">
> + <object class="GtkBox" id="box_dialog">
> + <property name="can_focus">False</property>
> + <property name="orientation">vertical</property>
> + <property name="spacing">2</property>
> + <child internal-child="action_area">
> + <object class="GtkButtonBox" id="dialog-action_area1">
> + <property name="can_focus">False</property>
> + </object>
> + <packing>
> + <property name="expand">False</property>
> + <property name="fill">False</property>
> + <property name="position">0</property>
> + </packing>
> + </child>
> + <child>
> + <object class="GtkGrid" id="main_grid">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="border_width">12</property>
> + <property name="row_spacing">12</property>
> + <property name="column_spacing">12</property>
> + <child>
> + <object class="GtkLabel" id="label_title">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="hexpand">True</property>
> + <property name="label" translatable="yes">To enable Livepatch choose an Ubuntu Single Sign-on account.</property>
> + <property name="wrap">True</property>
> + <property name="xalign">0</property>
> + </object>
> + <packing>
> + <property name="left_attach">0</property>
> + <property name="top_attach">0</property>
> + </packing>
> + </child>
> + <child>
> + <object class="GtkButton" id="button_settings">
> + <property name="visible">True</property>
> + <property name="can_focus">True</property>
> + <property name="receives_default">True</property>
> + <property name="tooltip_text" translatable="yes">Open online account settings</property>
> + <property name="valign">start</property>
> + <child>
> + <object class="GtkImage" id="settings_button_image">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="icon_name">emblem-system-symbolic</property>
> + </object>
> + </child>
> + </object>
> + <packing>
> + <property name="left_attach">1</property>
> + <property name="top_attach">0</property>
> + </packing>
> + </child>
> + <child>
> + <object class="GtkFrame" id="main_frame">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="label_xalign">0</property>
> + <child>
> + <object class="GtkListBox" id="listbox_accounts">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="selection_mode">none</property>
> + <child>
> + <object class="GtkListBoxRow" id="listboxrow_new_account">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <child>
> + <object class="GtkLabel" id="label_new_account">
> + <property name="height_request">48</property>
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="halign">center</property>
> + <property name="valign">center</property>
> + <property name="label" translatable="yes"><b>Use another account...</b></property>
> + <property name="use_markup">True</property>
> + <property name="justify">center</property>
> + </object>
> + </child>
> + </object>
> + </child>
> + </object>
> + </child>
> + </object>
> + <packing>
> + <property name="left_attach">0</property>
> + <property name="top_attach">1</property>
> + <property name="width">2</property>
> + </packing>
> + </child>
> + </object>
> + <packing>
> + <property name="expand">False</property>
> + <property name="fill">True</property>
> + <property name="position">1</property>
> + </packing>
> + </child>
> + </object>
> + </child>
> + <child type="titlebar">
> + <object class="GtkHeaderBar">
> + <property name="visible">True</property>
> + <property name="can_focus">False</property>
> + <property name="title">Choose an account</property>
I'm not very familiar with gtk but it is possible to make title translatable?
> + <child>
> + <object class="GtkButton" id="button_cancel">
> + <property name="label">gtk-cancel</property>
> + <property name="visible">True</property>
> + <property name="can_focus">True</property>
> + <property name="receives_default">True</property>
> + <property name="use_stock">True</property>
> + </object>
> + </child>
> + </object>
> + </child>
> + <action-widgets>
> + <action-widget response="-6">button_cancel</action-widget>
> + </action-widgets>
> + </object>
> +</interface>
>
> === modified file 'debian/changelog'
> --- debian/changelog 2018-03-06 16:06:38 +0000
> +++ debian/changelog 2018-03-16 13:02:28 +0000
> @@ -1,3 +1,17 @@
> +software-properties (0.96.24.25) UNRELEASED; urgency=medium
> +
> + * DialogAuth.py: Implement a dialog to choose between an ubuntu sso account or
> + login into a new one. This interacts with gnome-online-accounts.
> + * DialogLivepatchError: Implement a dialog to show livepatch reletated errors.
The word "related" has a typo in it.
> + * GoaAuth.py: Implement an utility function to store manage livepatch credentials.
> + * SoftwareProperties.py: Implement functions used by the dbus API to
> + enable/disable livepatch.
> + * SoftwarePropertiesDbus.py: Add SetLivepatchEnabled to the dbus API.
> + * SoftwarePropertiesGtk.py: Add a livepatch switch.
> + * debian/control: Depends on gir1.2-goa-1.0, gir1.2-snapd-1 and gir1.2-secret-1.
> +
> + -- Andrea Azzarone <andrea.azzarone at canonical.com> Wed, 14 Mar 2018 20:24:31 +0100
> +
> software-properties (0.96.24.23) bionic; urgency=medium
>
> * SoftwarePropertiesGtk.py: After installing proprietary drivers provide the
>
> === modified file 'softwareproperties/SoftwareProperties.py'
> --- softwareproperties/SoftwareProperties.py 2017-03-01 17:35:37 +0000
> +++ softwareproperties/SoftwareProperties.py 2018-03-16 13:02:28 +0000
> @@ -858,6 +866,128 @@
> 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='/var/snap/canonical-livepatch/common/machine-token')
> + 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 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',
> + channel='edge', # Remove this once bionic is officialy supported.
> + 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 cannot be installed"))
> +
> + 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 cannot be installed"))
> +
> + def enable_livepatch_service(self, token):
> + if self.is_livepatch_enabled():
The indenting from here on is 2 spaces instead of four, please make so that it is all 4 spaces.
> + 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 ""
> +
> + def disable_livepatch_service(self):
> + 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 ""
>
> def shortcut_handler(shortcut):
> for factory in _SHORTCUT_FACTORIES:
>
> === modified file 'softwareproperties/gtk/SoftwarePropertiesGtk.py'
> --- softwareproperties/gtk/SoftwarePropertiesGtk.py 2018-03-06 15:47:27 +0000
> +++ softwareproperties/gtk/SoftwarePropertiesGtk.py 2018-03-16 13:02:28 +0000
> @@ -1446,3 +1459,138 @@
> % {'count': self.nonfree_drivers})
> 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='/var/snap/canonical-livepatch/common/machine-token')
> + self.lp_monitor = file.monitor_file(Gio.FileMonitorFlags.NONE)
> +
> + # connect to signals
> + self.handlers[self.goa_auth] = \
> + self.goa_auth.connect('notify::logged', 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):
> + supported_releases = ['trusty', 'xenial', 'bionic']
Instead of hard coding releases I think adding a dependency on and using python3-distro-info would be better, this would also remove the need for an SRU when bionic becomes supported. These may help:
di = distro_info.UbuntuDistroInfo()
di.is_lts('bionic')
di.supported(datetime.now().date())
di.devel()
> + return any([self.distro.is_codename(d) for d in supported_releases])
> +
> + 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 = "<span color='red'>" + text + "</span>"
> + self.label_livepatch_login.set_markup(text)
> + else:
> + self.checkbutton_livepatch.set_sensitive(False)
> + self.button_ubuntuone.set_label(_('Sign In…'))
Somewhere else "Sign In" is capitalized like "Sign in", I've no preference but think it should be consistent.
> + 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
> +
> + if self.checkbutton_livepatch.get_active():
> + self.backend.SetLivepatchEnabled(True, self.goa_auth.token if self.goa_auth.token else '',
> + reply_handler=self.livepatch_enabled_reply_handler,
> + error_handler=self.livepatch_enabled_error_handler,
> + timeout=INFINIT_DBUS_TIMEOUT)
> + else:
> + self.backend.SetLivepatchEnabled(False, '',
> + reply_handler=self.livepatch_enabled_reply_handler,
> + error_handler=self.livepatch_enabled_error_handler,
> + timeout=INFINIT_DBUS_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)
> + if response == DialogLivepatchError.RESPONSE_SETTINGS:
> + self.window_main.show()
> + self.quit_when_livepatch_responds = False
> + else:
> + if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active():
> + self.backend.SetLivepatchEnabled(False, '',
> + reply_handler=self.livepatch_enabled_reply_handler,
> + error_handler=self.livepatch_enabled_error_handler)
> + elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active():
> + self.backend.SetLivepatchEnabled(True, self.goa_auth.token if self.goa_auth.token else '',
> + reply_handler=self.livepatch_enabled_reply_handler,
> + error_handler=self.livepatch_enabled_error_handler)
> + else:
> + self.waiting_livepatch_response = False
> +
> + if self.quit_when_livepatch_responds:
> + self.on_close_button(self.button_close)
--
https://code.launchpad.net/~azzar1/software-properties/add-canonical-livepatch/+merge/341427
Your team Ubuntu Core Development Team is requested to review the proposed merge of lp:~azzar1/software-properties/add-canonical-livepatch into lp:software-properties.
More information about the Ubuntu-reviews
mailing list