Merge lp:~azzar1/software-properties/livepatch-tab1 into lp:software-properties

Proposed by Andrea Azzarone
Status: Merged
Approved by: Sebastien Bacher
Approved revision: 1090
Merged at revision: 1068
Proposed branch: lp:~azzar1/software-properties/livepatch-tab1
Merge into: lp:software-properties
Diff against target: 2002 lines (+1206/-427) (has conflicts)
18 files modified
data/gtkbuilder/dialog-livepatch-error.ui (+96/-15)
data/gtkbuilder/main.ui (+221/-81)
data/icons/scalable/apps/livepatch.svg (+1/-0)
data/software-properties-livepatch.desktop.in (+12/-0)
debian/changelog (+14/-0)
debian/control (+2/-1)
debian/software-properties-gtk.install (+1/-0)
po/POTFILES.in (+6/-0)
setup.cfg (+1/-0)
softwareproperties/LivepatchService.py (+254/-0)
softwareproperties/LivepatchSnap.py (+135/-0)
softwareproperties/SoftwareProperties.py (+0/-152)
softwareproperties/dbus/SoftwarePropertiesDBus.py (+8/-2)
softwareproperties/gtk/DialogLivepatchError.py (+15/-6)
softwareproperties/gtk/LivepatchPage.py (+375/-0)
softwareproperties/gtk/SimpleGtkbuilderApp.py (+3/-0)
softwareproperties/gtk/SoftwarePropertiesGtk.py (+6/-169)
softwareproperties/gtk/utils.py (+56/-1)
Text conflict in debian/changelog
To merge this branch: bzr merge lp:~azzar1/software-properties/livepatch-tab1
Reviewer Review Type Date Requested Status
Sebastien Bacher Approve
Review via email: mp+362443@code.launchpad.net

Commit message

Move Livepatch GUI in a different tab as per: https://wiki.ubuntu.com/SoftwareUpdates#livepatch

Please note that this does not include the checkbutton to enable/disable the livepatch indicator.

To post a comment you must log in.
1073. By Andrea Azzarone

Add new files to po/POTFILES.in

1074. By Andrea Azzarone

The /status endpoint returns a code != 200 when canonical-livepatch expected an internal error. Considering this we need to try to parse the response no matter the response code.

1075. By Andrea Azzarone

Update the generic error string.

1076. By Andrea Azzarone

Remove debug string.

1077. By Andrea Azzarone

Properly format the datetime of the last check.

1078. By Andrea Azzarone

Enable livepatch automatically after login.

1079. By Andrea Azzarone

Show "Last check for updates: None yet" and "No updates currently applied" while Livepatch is being enabled.

1080. By Andrea Azzarone

Show "No updates currently applied" if status is "unapplied". Also move the trigger logic in one place.

Revision history for this message
Andrea Azzarone (azzar1) wrote :
1081. By Andrea Azzarone

Make sure the switch is not on if it's not senstive.

1082. By Andrea Azzarone

Show a "Livepatch requires an Internet connection." if the computer is not fully connected.

Revision history for this message
Sebastien Bacher (seb128) wrote :

I didn't really review it yet, but one comment from a missing ',' which impacting the build

review: Needs Fixing
Revision history for this message
Sebastien Bacher (seb128) wrote :

Ok, some extra inline comment, also

- you inverted the driver/devel option tabs, is there a design request for that (https://wiki.ubuntu.com/SoftwareUpdates#livepatch doesn't include the devel options tab)

- when you have an account, if you click 'disconnect and then do 'connect' when connecting it also displays that warning
'Failed to get Livepatch status: Expecting value: line 1 column 1 (char 0)',

the UI seems to work normally so it's minor/might just be noise but the warning is a bit confusing on what the problem is

- would it make sense to add some extra keywords to the .desktop like 'security' and 'update'?

Code seems fine otherwise and to work as intended, good work!

review: Needs Fixing
1083. By Andrea Azzarone

Add missing comma.

1084. By Andrea Azzarone

Get the attributes out of the translatable string.

1085. By Andrea Azzarone

Make GtkScrolledWindow of textview_livepatch hidden by default.

1086. By Andrea Azzarone

Make "Livepatch" string non transatable.

1087. By Andrea Azzarone

Add 'security' and 'update' keyword to data/software-properties-livepatch.desktop.in

1088. By Andrea Azzarone

Revert accidental change that switched the position of "Additional driver" and "Developer options" tabs.

1089. By Andrea Azzarone

Use logging.debug instead of logging.warning.

Revision history for this message
Andrea Azzarone (azzar1) wrote :

Hi,

thanks for the review.

> Ok, some extra inline comment, also

All of them should be fixed now.

>
> - you inverted the driver/devel option tabs, is there a design request for
> that (https://wiki.ubuntu.com/SoftwareUpdates#livepatch doesn't include the
> devel options tab)

That was accidental. It should be fixed now.

>
> - when you have an account, if you click 'disconnect and then do 'connect'
> when connecting it also displays that warning
> 'Failed to get Livepatch status: Expecting value: line 1 column 1 (char 0)',

I changed it from a warning to a debug string.

>
> the UI seems to work normally so it's minor/might just be noise but the
> warning is a bit confusing on what the problem is
>
> - would it make sense to add some extra keywords to the .desktop like
> 'security' and 'update'?
>

Done.

>
> Code seems fine otherwise and to work as intended, good work!

Revision history for this message
Andrea Azzarone (azzar1) :
Revision history for this message
Sebastien Bacher (seb128) wrote :

Looks good now, you still have one translatable 'Livepatch' though

review: Needs Fixing
1090. By Andrea Azzarone

Don't make Livepatch tab name translatable.

Revision history for this message
Andrea Azzarone (azzar1) wrote :

Fixed.

Revision history for this message
Sebastien Bacher (seb128) wrote :

Thanks

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'data/gtkbuilder/dialog-livepatch-error.ui'
2--- data/gtkbuilder/dialog-livepatch-error.ui 2017-10-11 18:23:42 +0000
3+++ data/gtkbuilder/dialog-livepatch-error.ui 2019-02-18 16:16:34 +0000
4@@ -1,58 +1,139 @@
5 <?xml version="1.0" encoding="UTF-8"?>
6-<!-- Generated with glade 3.18.3 -->
7+<!-- Generated with glade 3.22.0 -->
8 <interface>
9 <requires lib="gtk+" version="3.12"/>
10- <object class="GtkMessageDialog" id="messagedialog_livepatch">
11- <property name="can_focus">False</property>
12+ <object class="GtkTextBuffer" id="textbuffer_message"/>
13+ <object class="GtkDialog" id="messagedialog_livepatch">
14+ <property name="title">Livepatch</property>
15+ <property name="resizable">False</property>
16+ <property name="modal">True</property>
17 <property name="type_hint">dialog</property>
18- <property name="message_type">error</property>
19- <property name="text" translatable="yes">Sorry, there’s been a problem in setting up Canonical Livepatch.</property>
20+ <property name="urgency_hint">True</property>
21+ <property name="deletable">False</property>
22+ <property name="skip_taskbar_hint">True</property>
23+ <property name="skip_pager_hint">True</property>
24 <child internal-child="vbox">
25- <object class="GtkBox" id="messagedialog-vbox1">
26- <property name="can_focus">False</property>
27+ <object class="GtkBox">
28 <property name="orientation">vertical</property>
29 <property name="spacing">2</property>
30 <child internal-child="action_area">
31- <object class="GtkButtonBox" id="messagedialog-action_area1">
32- <property name="can_focus">False</property>
33+ <object class="GtkButtonBox">
34 <property name="layout_style">end</property>
35 <child>
36 <object class="GtkButton" id="button_settings">
37 <property name="label" translatable="yes">Settings…</property>
38- <property name="visible">True</property>
39 <property name="can_focus">True</property>
40- <property name="receives_default">True</property>
41+ <property name="receives_default">False</property>
42 <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/>
43 </object>
44 <packing>
45 <property name="expand">True</property>
46 <property name="fill">True</property>
47- <property name="position">0</property>
48 </packing>
49 </child>
50 <child>
51 <object class="GtkButton" id="button_ignore">
52 <property name="label" translatable="yes">Ignore</property>
53 <property name="visible">True</property>
54+ <property name="has_focus">True</property>
55 <property name="can_focus">True</property>
56 <property name="receives_default">True</property>
57- <property name="yalign">0.51999998092651367</property>
58 <signal name="clicked" handler="on_button_ignore_clicked" swapped="no"/>
59 </object>
60 <packing>
61 <property name="expand">True</property>
62 <property name="fill">True</property>
63- <property name="position">1</property>
64 </packing>
65 </child>
66 </object>
67 <packing>
68 <property name="expand">False</property>
69 <property name="fill">False</property>
70- <property name="position">0</property>
71+ </packing>
72+ </child>
73+ <child>
74+ <object class="GtkBox">
75+ <property name="visible">True</property>
76+ <property name="border_width">12</property>
77+ <property name="orientation">vertical</property>
78+ <child>
79+ <object class="GtkGrid">
80+ <property name="visible">True</property>
81+ <property name="row_spacing">12</property>
82+ <property name="column_spacing">12</property>
83+ <child>
84+ <object class="GtkLabel" id="label_primary">
85+ <property name="visible">True</property>
86+ <property name="xalign">0</property>
87+ </object>
88+ <packing>
89+ <property name="left_attach">1</property>
90+ <property name="top_attach">0</property>
91+ </packing>
92+ </child>
93+ <child>
94+ <object class="GtkImage">
95+ <property name="visible">True</property>
96+ <property name="halign">center</property>
97+ <property name="valign">start</property>
98+ <property name="stock">gtk-dialog-error</property>
99+ <property name="use_fallback">True</property>
100+ <property name="icon_size">6</property>
101+ </object>
102+ <packing>
103+ <property name="left_attach">0</property>
104+ <property name="top_attach">0</property>
105+ <property name="height">3</property>
106+ </packing>
107+ </child>
108+ <child>
109+ <object class="GtkLabel">
110+ <property name="visible">True</property>
111+ <property name="label" translatable="yes">The error was:</property>
112+ <property name="xalign">0</property>
113+ </object>
114+ <packing>
115+ <property name="left_attach">1</property>
116+ <property name="top_attach">1</property>
117+ </packing>
118+ </child>
119+ <child>
120+ <object class="GtkTextView" id="treeview_message">
121+ <property name="height_request">100</property>
122+ <property name="visible">True</property>
123+ <property name="hexpand">True</property>
124+ <property name="vexpand">True</property>
125+ <property name="pixels_above_lines">6</property>
126+ <property name="pixels_below_lines">6</property>
127+ <property name="editable">False</property>
128+ <property name="wrap_mode">word</property>
129+ <property name="left_margin">6</property>
130+ <property name="right_margin">6</property>
131+ <property name="cursor_visible">False</property>
132+ <property name="buffer">textbuffer_message</property>
133+ <property name="accepts_tab">False</property>
134+ </object>
135+ <packing>
136+ <property name="left_attach">1</property>
137+ <property name="top_attach">2</property>
138+ </packing>
139+ </child>
140+ </object>
141+ <packing>
142+ <property name="expand">True</property>
143+ <property name="fill">True</property>
144+ </packing>
145+ </child>
146+ </object>
147+ <packing>
148+ <property name="expand">True</property>
149+ <property name="fill">True</property>
150 </packing>
151 </child>
152 </object>
153 </child>
154+ <child type="titlebar">
155+ <placeholder/>
156+ </child>
157 </object>
158 </interface>
159
160=== modified file 'data/gtkbuilder/main.ui'
161--- data/gtkbuilder/main.ui 2017-08-24 14:40:18 +0000
162+++ data/gtkbuilder/main.ui 2019-02-18 16:16:34 +0000
163@@ -1,7 +1,13 @@
164 <?xml version="1.0" encoding="UTF-8"?>
165-<!-- Generated with glade 3.18.3 -->
166+<!-- Generated with glade 3.22.0 -->
167 <interface>
168- <requires lib="gtk+" version="3.0"/>
169+ <requires lib="gtk+" version="3.22"/>
170+ <object class="GtkListStore" id="model_livepatch_fixes">
171+ <columns>
172+ <!-- column-name fix -->
173+ <column type="gchararray"/>
174+ </columns>
175+ </object>
176 <object class="GtkListStore" id="model_normal_updates_display">
177 <columns>
178 <!-- column-name text -->
179@@ -91,6 +97,7 @@
180 <object class="GtkTextBuffer" id="textbuffer1">
181 <property name="text" translatable="yes">To install from a CD-ROM or DVD, insert the medium into the drive.</property>
182 </object>
183+ <object class="GtkTextBuffer" id="textbuffer_livepatch"/>
184 <object class="GtkWindow" id="window_main">
185 <property name="can_focus">False</property>
186 <property name="border_width">6</property>
187@@ -696,84 +703,6 @@
188 </packing>
189 </child>
190 <child>
191- <object class="GtkAlignment" id="alignment3">
192- <property name="visible">True</property>
193- <property name="can_focus">False</property>
194- <property name="left_padding">12</property>
195- <child>
196- <object class="GtkGrid" id="grid_livepatch">
197- <property name="visible">True</property>
198- <property name="can_focus">False</property>
199- <property name="halign">center</property>
200- <property name="hexpand">False</property>
201- <property name="vexpand">False</property>
202- <property name="row_spacing">6</property>
203- <property name="column_spacing">6</property>
204- <child>
205- <object class="GtkBox" id="hbox_livepatch">
206- <property name="visible">True</property>
207- <property name="can_focus">False</property>
208- <property name="halign">center</property>
209- <property name="spacing">6</property>
210- <child>
211- <object class="GtkLabel" id="label_livepatch_login">
212- <property name="visible">True</property>
213- <property name="can_focus">False</property>
214- </object>
215- <packing>
216- <property name="expand">False</property>
217- <property name="fill">True</property>
218- <property name="position">0</property>
219- </packing>
220- </child>
221- <child>
222- <object class="GtkButton" id="button_ubuntuone">
223- <property name="visible">True</property>
224- <property name="can_focus">True</property>
225- <property name="receives_default">True</property>
226- <property name="xalign">0</property>
227- </object>
228- <packing>
229- <property name="expand">False</property>
230- <property name="fill">True</property>
231- <property name="position">2</property>
232- </packing>
233- </child>
234- </object>
235- <packing>
236- <property name="left_attach">1</property>
237- <property name="top_attach">1</property>
238- </packing>
239- </child>
240- <child>
241- <object class="GtkCheckButton" id="checkbutton_livepatch">
242- <property name="label" translatable="yes">Use Canonical Livepatch to increase security between restarts</property>
243- <property name="use_action_appearance">False</property>
244- <property name="visible">True</property>
245- <property name="can_focus">True</property>
246- <property name="receives_default">False</property>
247- <property name="use_underline">True</property>
248- <property name="xalign">0</property>
249- <property name="draw_indicator">True</property>
250- </object>
251- <packing>
252- <property name="left_attach">1</property>
253- <property name="top_attach">0</property>
254- </packing>
255- </child>
256- <child>
257- <placeholder/>
258- </child>
259- </object>
260- </child>
261- </object>
262- <packing>
263- <property name="expand">True</property>
264- <property name="fill">True</property>
265- <property name="position">2</property>
266- </packing>
267- </child>
268- <child>
269 <object class="GtkAlignment" id="alignment15">
270 <property name="visible">True</property>
271 <property name="can_focus">False</property>
272@@ -820,7 +749,7 @@
273 <packing>
274 <property name="expand">False</property>
275 <property name="fill">False</property>
276- <property name="position">3</property>
277+ <property name="position">2</property>
278 </packing>
279 </child>
280 </object>
281@@ -1187,6 +1116,214 @@
282 <property name="tab_fill">False</property>
283 </packing>
284 </child>
285+ <child>
286+ <object class="GtkBox" id="vbox_livepatch">
287+ <property name="visible">True</property>
288+ <property name="can_focus">False</property>
289+ <property name="border_width">12</property>
290+ <property name="orientation">vertical</property>
291+ <property name="spacing">12</property>
292+ <child>
293+ <object class="GtkLabel" id="label_livepatch_description">
294+ <property name="visible">True</property>
295+ <property name="can_focus">False</property>
296+ <property name="label" translatable="yes">Canonical Livepatch helps keep your system secure by applying security updates that don't require a restart. &lt;a href="https://www.ubuntu.com/livepatch"&gt;Learn More&lt;/a&gt;</property>
297+ <property name="use_markup">True</property>
298+ <property name="wrap">True</property>
299+ <property name="max_width_chars">1</property>
300+ <property name="xalign">0</property>
301+ </object>
302+ <packing>
303+ <property name="expand">False</property>
304+ <property name="fill">True</property>
305+ <property name="position">0</property>
306+ </packing>
307+ </child>
308+ <child>
309+ <object class="GtkBox" id="hbox_switch">
310+ <property name="visible">True</property>
311+ <property name="can_focus">False</property>
312+ <property name="spacing">6</property>
313+ <child>
314+ <object class="GtkSwitch" id="switch_livepatch">
315+ <property name="visible">True</property>
316+ <property name="sensitive">False</property>
317+ <property name="can_focus">True</property>
318+ </object>
319+ <packing>
320+ <property name="expand">False</property>
321+ <property name="fill">True</property>
322+ <property name="position">0</property>
323+ </packing>
324+ </child>
325+ <child>
326+ <object class="GtkSpinner" id="spinner_livepatch">
327+ <property name="can_focus">False</property>
328+ </object>
329+ <packing>
330+ <property name="expand">False</property>
331+ <property name="fill">True</property>
332+ <property name="position">1</property>
333+ </packing>
334+ </child>
335+ <child>
336+ <object class="GtkLabel" id="label_livepatch_switch">
337+ <property name="visible">True</property>
338+ <property name="can_focus">False</property>
339+ </object>
340+ <packing>
341+ <property name="expand">False</property>
342+ <property name="fill">True</property>
343+ <property name="position">2</property>
344+ </packing>
345+ </child>
346+ <child>
347+ <object class="GtkButton" id="button_livepatch_login">
348+ <property name="can_focus">True</property>
349+ <property name="receives_default">True</property>
350+ </object>
351+ <packing>
352+ <property name="expand">False</property>
353+ <property name="fill">True</property>
354+ <property name="pack_type">end</property>
355+ <property name="position">3</property>
356+ </packing>
357+ </child>
358+ </object>
359+ <packing>
360+ <property name="expand">False</property>
361+ <property name="fill">True</property>
362+ <property name="position">1</property>
363+ </packing>
364+ </child>
365+ <child>
366+ <object class="GtkStack" id="stack_livepatch">
367+ <property name="can_focus">False</property>
368+ <property name="transition_type">crossfade</property>
369+ <property name="interpolate_size">True</property>
370+ <child>
371+ <object class="GtkScrolledWindow">
372+ <property name="visible">False</property>
373+ <property name="can_focus">True</property>
374+ <property name="shadow_type">in</property>
375+ <child>
376+ <object class="GtkTextView" id="textview_livepatch">
377+ <property name="visible">True</property>
378+ <property name="can_focus">True</property>
379+ <property name="pixels_above_lines">6</property>
380+ <property name="editable">False</property>
381+ <property name="wrap_mode">word</property>
382+ <property name="left_margin">6</property>
383+ <property name="right_margin">6</property>
384+ <property name="cursor_visible">False</property>
385+ <property name="buffer">textbuffer_livepatch</property>
386+ <property name="accepts_tab">False</property>
387+ </object>
388+ </child>
389+ </object>
390+ <packing>
391+ <property name="name">page_livepatch_message</property>
392+ </packing>
393+ </child>
394+ <child>
395+ <object class="GtkBox">
396+ <property name="visible">True</property>
397+ <property name="can_focus">False</property>
398+ <property name="orientation">vertical</property>
399+ <property name="spacing">12</property>
400+ <child>
401+ <object class="GtkLabel" id="label_livepatch_last_update">
402+ <property name="visible">True</property>
403+ <property name="can_focus">False</property>
404+ <property name="xalign">0</property>
405+ </object>
406+ <packing>
407+ <property name="expand">False</property>
408+ <property name="fill">True</property>
409+ <property name="position">0</property>
410+ </packing>
411+ </child>
412+ <child>
413+ <object class="GtkLabel" id="label_livepatch_header">
414+ <property name="visible">True</property>
415+ <property name="can_focus">False</property>
416+ <property name="xalign">0</property>
417+ </object>
418+ <packing>
419+ <property name="expand">False</property>
420+ <property name="fill">True</property>
421+ <property name="position">1</property>
422+ </packing>
423+ </child>
424+ <child>
425+ <object class="GtkScrolledWindow" id="scrolledwindow_livepatch_fixes">
426+ <property name="visible">True</property>
427+ <property name="can_focus">True</property>
428+ <property name="shadow_type">in</property>
429+ <child>
430+ <object class="GtkTreeView" id="treeview_livepatch">
431+ <property name="visible">True</property>
432+ <property name="can_focus">True</property>
433+ <property name="model">model_livepatch_fixes</property>
434+ <property name="headers_visible">False</property>
435+ <property name="enable_search">False</property>
436+ <property name="show_expanders">False</property>
437+ <child internal-child="selection">
438+ <object class="GtkTreeSelection"/>
439+ </child>
440+ <child>
441+ <object class="GtkTreeViewColumn">
442+ <property name="title" translatable="yes">column</property>
443+ <child>
444+ <object class="GtkCellRendererText">
445+ <property name="width_chars">100</property>
446+ <property name="wrap_mode">word</property>
447+ <property name="wrap_width">100</property>
448+ </object>
449+ <attributes>
450+ <attribute name="markup">0</attribute>
451+ </attributes>
452+ </child>
453+ </object>
454+ </child>
455+ </object>
456+ </child>
457+ </object>
458+ <packing>
459+ <property name="expand">True</property>
460+ <property name="fill">True</property>
461+ <property name="position">2</property>
462+ </packing>
463+ </child>
464+ </object>
465+ <packing>
466+ <property name="name">page_livepatch_status</property>
467+ <property name="position">1</property>
468+ </packing>
469+ </child>
470+ </object>
471+ <packing>
472+ <property name="expand">True</property>
473+ <property name="fill">True</property>
474+ <property name="position">2</property>
475+ </packing>
476+ </child>
477+ </object>
478+ <packing>
479+ <property name="position">6</property>
480+ </packing>
481+ </child>
482+ <child type="tab">
483+ <object class="GtkLabel" id="label_livepatch">
484+ <property name="visible">True</property>
485+ <property name="can_focus">False</property>
486+ <property name="label">Livepatch</property>
487+ </object>
488+ <packing>
489+ <property name="position">6</property>
490+ <property name="tab_fill">False</property>
491+ </packing>
492+ </child>
493 </object>
494 <packing>
495 <property name="expand">True</property>
496@@ -1246,6 +1383,9 @@
497 </child>
498 </object>
499 </child>
500+ <child type="titlebar">
501+ <placeholder/>
502+ </child>
503 </object>
504 <object class="GtkSizeGroup" id="sizegroup1">
505 <widgets>
506
507=== added file 'data/icons/16x16/apps/livepatch.svg'
508Binary files data/icons/16x16/apps/livepatch.svg 1970-01-01 00:00:00 +0000 and data/icons/16x16/apps/livepatch.svg 2019-02-18 16:16:34 +0000 differ
509=== added file 'data/icons/24x24/apps/livepatch.svg'
510Binary files data/icons/24x24/apps/livepatch.svg 1970-01-01 00:00:00 +0000 and data/icons/24x24/apps/livepatch.svg 2019-02-18 16:16:34 +0000 differ
511=== added file 'data/icons/48x48/apps/livepatch.svg'
512Binary files data/icons/48x48/apps/livepatch.svg 1970-01-01 00:00:00 +0000 and data/icons/48x48/apps/livepatch.svg 2019-02-18 16:16:34 +0000 differ
513=== added file 'data/icons/64x64/apps/livepatch.svg'
514Binary files data/icons/64x64/apps/livepatch.svg 1970-01-01 00:00:00 +0000 and data/icons/64x64/apps/livepatch.svg 2019-02-18 16:16:34 +0000 differ
515=== added file 'data/icons/scalable/apps/livepatch.svg'
516--- data/icons/scalable/apps/livepatch.svg 1970-01-01 00:00:00 +0000
517+++ data/icons/scalable/apps/livepatch.svg 2019-02-18 16:16:34 +0000
518@@ -0,0 +1,1 @@
519+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="Layer_1" x="0px" y="0px" width="400px" height="400px" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400;" xml:space="preserve"> <style type="text/css"> .st0{fill:#E95420;} </style> <path class="st0" d="M200,0C89.7,0,0,89.7,0,200s89.7,200,200,200s200-89.7,200-200S310.3,0,200,0L200,0z M200,325 c-64.6,0-117.9-49.3-124.4-112.2v-0.1H38.8c-5.1,0-9.6-3-11.5-7.7s-0.9-10.1,2.7-13.6l61.2-61.3c4.9-4.9,12.8-4.9,17.7,0l60.6,60.6 c2.6,2.3,4.3,5.7,4.3,9.5c0,6.9-5.6,12.5-12.5,12.5h-35.2c6,35.4,36.9,62.4,73.9,62.4c41.4,0,75-33.6,75-75c0-41.3-33.6-75-75-75 c-13.8,0-25-11.2-25-25s11.2-25,25-25c68.9,0,125,56.1,125,125C325,268.9,268.9,325,200,325z"/> </svg>
520\ No newline at end of file
521
522=== added file 'data/software-properties-livepatch.desktop.in'
523--- data/software-properties-livepatch.desktop.in 1970-01-01 00:00:00 +0000
524+++ data/software-properties-livepatch.desktop.in 2019-02-18 16:16:34 +0000
525@@ -0,0 +1,12 @@
526+[Desktop Entry]
527+Keywords=Livepatch;
528+Exec=/usr/bin/software-properties-gtk --open-tab=6
529+Icon=livepatch
530+Terminal=false
531+Type=Application
532+OnlyShowIn=GNOME;
533+Categories=GTK;Settings;HardwareSettings
534+X-AppStream-Ignore=true
535+Name=Livepatch
536+_Comment=Manage Canonical Livepatch
537+_Keywords=Security;Update;
538
539=== modified file 'debian/changelog'
540--- debian/changelog 2019-02-13 00:00:29 +0000
541+++ debian/changelog 2019-02-18 16:16:34 +0000
542@@ -1,3 +1,4 @@
543+<<<<<<< TREE
544 software-properties (0.97.2) disco; urgency=medium
545
546 * Install python3-aptdaemon with software-properties-qt.
547@@ -10,6 +11,19 @@
548
549 -- Hans P. Möller <hmollercl@lubuntu.me> Sat, 09 Feb 2019 17:08:39 -0600
550
551+=======
552+software-properties (0.96.33) UNRELEASED; urgency=medium
553+
554+ * Move Livepatch UI in a different tab.
555+ * Add a Livepatch desktop file.
556+ * debian/control:
557+ - Remove gir1.2-secret-1 dep, it is no longer needed.
558+ - Add python3-dateutil and python3-requests-unixsocket dep, they are
559+ required by LivepatchService.py.
560+
561+ -- Andrea Azzarone <andrea.azzarone@canonical.com> Tue, 29 Jan 2019 18:29:18 +0000
562+
563+>>>>>>> MERGE-SOURCE
564 software-properties (0.96.32) disco; urgency=medium
565
566 * softwareproperties/gtk/DialogAuth.py:
567
568=== modified file 'debian/control'
569--- debian/control 2019-02-13 00:00:29 +0000
570+++ debian/control 2019-02-18 16:16:34 +0000
571@@ -57,10 +57,11 @@
572 python3-gi,
573 gir1.2-gtk-3.0,
574 gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1),
575- gir1.2-secret-1,
576 gir1.2-snapd-1,
577 python3-aptdaemon.gtk3widgets,
578+ python3-dateutil,
579 python3-distro-info,
580+ python3-requests-unixsocket,
581 software-properties-common,
582 ubuntu-drivers-common (>= 1:0.2.75),
583 python3-gi,
584
585=== modified file 'debian/software-properties-gtk.install'
586--- debian/software-properties-gtk.install 2018-04-17 11:36:55 +0000
587+++ debian/software-properties-gtk.install 2019-02-18 16:16:34 +0000
588@@ -5,6 +5,7 @@
589 debian/tmp/usr/share/icons
590 debian/tmp/usr/share/applications/software-properties-gtk.desktop
591 debian/tmp/usr/share/applications/software-properties-drivers.desktop
592+debian/tmp/usr/share/applications/software-properties-livepatch.desktop
593 debian/tmp/usr/share/metainfo/software-properties-gtk.appdata.xml
594 debian/tmp/usr/share/glib-2.0/schemas
595 #debian/tmp/usr/share/gnome/help/software-properties
596
597=== modified file 'po/POTFILES.in'
598--- po/POTFILES.in 2018-07-14 10:11:32 +0000
599+++ po/POTFILES.in 2019-02-18 16:16:34 +0000
600@@ -5,6 +5,7 @@
601 data/software-properties-gtk.appdata.xml.in
602 data/software-properties-qt.desktop.in
603 data/software-properties-drivers.desktop.in
604+data/software-properties-livepatch.desktop.in
605 software-properties-gtk
606 software-properties-qt
607 add-apt-repository
608@@ -28,8 +29,12 @@
609 softwareproperties/gtk/DialogEdit.py
610 softwareproperties/gtk/DialogAdd.py
611 softwareproperties/gtk/DialogCacheOutdated.py
612+softwareproperties/gtk/DialogLivepatchError.py
613+softwareproperties/gtk/LivepatchPage.py
614 softwareproperties/CountryInformation.py
615 softwareproperties/AptAuth.py
616+softwareproperties/LivepatchService.py
617+softwareproperties/LivepatchSnap.py
618 [type: gettext/glade]data/designer/dialog_mirror.ui
619 [type: gettext/glade]data/designer/dialog_edit.ui
620 [type: gettext/glade]data/designer/main.ui
621@@ -41,3 +46,4 @@
622 [type: gettext/glade]data/gtkbuilder/dialog-mirror.ui
623 [type: gettext/glade]data/gtkbuilder/dialog-add.ui
624 [type: gettext/glade]data/gtkbuilder/dialog-auth.ui
625+[type: gettext/glade]data/gtkbuilder/dialog-livepatch-error.ui
626
627=== modified file 'setup.cfg'
628--- setup.cfg 2018-07-14 10:11:32 +0000
629+++ setup.cfg 2019-02-18 16:16:34 +0000
630@@ -4,6 +4,7 @@
631 desktop_files=[("share/applications",
632 ("data/software-properties-gtk.desktop.in",
633 "data/software-properties-drivers.desktop.in",
634+ "data/software-properties-livepatch.desktop.in",
635 "data/software-properties-qt.desktop.in",),
636 )
637 ]
638
639=== added file 'softwareproperties/LivepatchService.py'
640--- softwareproperties/LivepatchService.py 1970-01-01 00:00:00 +0000
641+++ softwareproperties/LivepatchService.py 2019-02-18 16:16:34 +0000
642@@ -0,0 +1,254 @@
643+#
644+# Copyright (c) 2019 Canonical
645+#
646+# Authors:
647+# Andrea Azzarone <andrea.azzarone@canonical.com>
648+#
649+# This program is free software; you can redistribute it and/or
650+# modify it under the terms of the GNU General Public License as
651+# published by the Free Software Foundation; either version 2 of the
652+# License, or (at your option) any later version.
653+#
654+# This program is distributed in the hope that it will be useful,
655+# but WITHOUT ANY WARRANTY; without even the implied warranty of
656+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
657+# GNU General Public License for more details.
658+#
659+# You should have received a copy of the GNU General Public License
660+# along with this program; if not, write to the Free Software
661+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
662+# USA
663+
664+from gettext import gettext as _
665+import logging
666+
667+import gi
668+from gi.repository import Gio, GLib, GObject
669+
670+try:
671+ import dateutil.parser
672+ import requests_unixsocket
673+
674+ gi.require_version('Snapd', '1')
675+ from gi.repository import Snapd
676+except(ImportError, ValueError):
677+ pass
678+
679+from softwareproperties.gtk.utils import (
680+ has_gnome_online_accounts,
681+ is_current_distro_lts,
682+ is_current_distro_supported,
683+ retry
684+)
685+
686+from softwareproperties.LivepatchSnap import LivepatchSnap
687+
688+
689+def datetime_parser(json_dict):
690+ for (key, value) in json_dict.items():
691+ try:
692+ json_dict[key] = dateutil.parser.parse(value)
693+ except (ValueError, TypeError):
694+ pass
695+ return json_dict
696+
697+class LivepatchAvailability:
698+ FALSE = 0
699+ TRUE = 1
700+ NO_CONNECTIVITY=3
701+ CHECKING = 2
702+
703+
704+class LivepatchService(GObject.GObject):
705+
706+ # Constants
707+ STATUS_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd.sock/status'
708+ ENABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/enable'
709+ DISABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable'
710+ LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
711+
712+ ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}')
713+ DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}')
714+
715+ # GObject.GObject
716+ __gproperties__ = {
717+ 'availability': (
718+ int, None, None,
719+ LivepatchAvailability.FALSE,
720+ LivepatchAvailability.CHECKING,
721+ LivepatchAvailability.FALSE,
722+ GObject.PARAM_READABLE),
723+ 'availability-message': (
724+ str, None, None, None, GObject.PARAM_READABLE),
725+ 'enabled': (
726+ bool, None, None, False, GObject.PARAM_READABLE),
727+ }
728+
729+ def __init__(self):
730+ GObject.GObject.__init__(self)
731+
732+ self._timeout_id = 0
733+
734+ self._snap = LivepatchSnap()
735+ self._session = requests_unixsocket.Session()
736+
737+ # Init Properties
738+ self._availability = LivepatchAvailability.FALSE
739+ self._availability_message = None
740+ lp_file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
741+ self._enabled = lp_file.query_exists()
742+
743+ # Monitor connectivity status
744+ self._nm = Gio.NetworkMonitor.get_default()
745+ self._nm.connect('notify::connectivity', self._network_changed_cb)
746+
747+ # Monitor status of canonical-livepatch
748+ self._lp_monitor = lp_file.monitor_file(Gio.FileMonitorFlags.NONE)
749+ self._lp_monitor.connect('changed', self._livepatch_enabled_changed_cb)
750+
751+ def do_get_property(self, pspec):
752+ if pspec.name == 'availability':
753+ return self._availability
754+ elif pspec.name == 'availability-message':
755+ return self._availability_message
756+ elif pspec.name == 'enabled':
757+ return self._enabled
758+ else:
759+ raise AssertionError
760+
761+ # Public API
762+ def trigger_availability_check(self):
763+ """Trigger a Livepatch availability check to be executed after a short
764+ timeout. Multiple triggers will result in a single request.
765+
766+ A notify::availability will be emitted when the check starts, and
767+ another one when the check ends.
768+ """
769+ def _update_availability():
770+ # each rule is a tuple of two elements, a callable and a string. The
771+ # string rapresents the error message that needs to be shown if the
772+ # callable returns false.
773+ rules = [
774+ (lambda: self._snap.get_status() != Snapd.SnapStatus.UNKNOWN,
775+ _('Canonical Livepatch snap is not available.')),
776+ (has_gnome_online_accounts,
777+ _('Gnome Online Accounts is required to enable Livepatch.')),
778+ (is_current_distro_lts,
779+ _('Livepatch is not available for this release.')),
780+ (is_current_distro_supported,
781+ _('The current release is no longer supported.'))]
782+
783+ if self._nm.props.connectivity != Gio.NetworkConnectivity.FULL:
784+ self._availability = LivepatchAvailability.NO_CONNECTIVITY
785+ self._availability_message = None
786+ else:
787+ for func, message in rules:
788+ if not func():
789+ self._availability = LivepatchAvailability.FALSE
790+ self._availability_message = message
791+ break
792+ else:
793+ self._availability = LivepatchAvailability.TRUE
794+ self._availability_message = None
795+
796+ self.notify('availability')
797+ self.notify('availability-message')
798+
799+ self._timeout_id = 0
800+ return False
801+
802+ self._availability = LivepatchAvailability.CHECKING
803+ self._availability_message = None
804+ self.notify('availability')
805+ self.notify('availability-message')
806+
807+ if self._timeout_id == 0:
808+ self._timeout_id = GLib.timeout_add_seconds(3, _update_availability)
809+
810+ def set_enabled(self, enabled, token):
811+ """Enable or disable Canonical Livepatch in the current system. This
812+ function will return once the operation succeeded or failed.
813+
814+ Args:
815+ enabled(bool): wheater to enable or disable the service.
816+ token(str): the authentication token to be used to enable Canonical
817+ Livepatch service.
818+
819+ Returns:
820+ (False, '') if successful, (True, error_message) otherwise.
821+ """
822+ if self._enabled == enabled:
823+ return False, ''
824+
825+ if not enabled:
826+ return self._disable_service()
827+ elif self._snap.get_status() == Snapd.SnapStatus.ACTIVE:
828+ return self._enable_service(token)
829+ else:
830+ success, msg = self._snap.enable_or_install()
831+ return self._enable_service(token) if success else (True, msg)
832+
833+ def get_status(self):
834+ """Synchronously retrieve the status of Canonical Livepatch.
835+
836+ Returns:
837+ str: The status. A valid string for success, None otherwise.
838+ """
839+ try:
840+ params = {'verbosity': 3, 'format': 'json'}
841+ r = self._session.get(self.STATUS_ENDPOINT, params=params)
842+ return r.json(object_hook=datetime_parser)
843+ except Exception as e:
844+ logging.debug('Failed to get Livepatch status: {}'.format(str(e)))
845+ return None
846+
847+ # Private methods
848+ def _enable_service(self, token):
849+ """Enable Canonical Livepatch in the current system. This function will
850+ return once the operation succeeded or failed.
851+
852+ Args:
853+ token(str): the authentication token to be used to enable Canonical
854+ Livepatch service.
855+
856+ Returns:
857+ (False, '') if successful, (True, error_message) otherwise.
858+ """
859+ try:
860+ return self._enable_service_with_retry(token)
861+ except Exception as e:
862+ return True, self.ENABLE_ERROR_MSG.format(str(e))
863+
864+ @retry(Exception)
865+ def _enable_service_with_retry(self, token):
866+ params = {'auth-token': token}
867+ r = self._session.put(self.ENABLE_ENDPOINT, params=params)
868+ return not r.ok, '' if r.ok else self.ENABLE_ERROR_MSG.format(r.text)
869+
870+ def _disable_service(self):
871+ """Disable Canonical Livepatch in the current system. This function will
872+ return once the operation succeeded or failed.
873+
874+ Returns:
875+ (False, '') if successful, (True, error_message) otherwise.
876+ """
877+ try:
878+ return self._disable_service_with_retry()
879+ except Exception as e:
880+ return True, self.DISABLE_ERROR_MSG.format(str(e))
881+
882+
883+ @retry(Exception)
884+ def _disable_service_with_retry(self):
885+ r = self._session.put(self.DISABLE_ENDPOINT)
886+ return not r.ok, '' if r.ok else self.DISABLE_ERROR_MSG.format(r.text)
887+
888+ # Signals handlers
889+ def _network_changed_cb(self, monitor, network_available):
890+ self.trigger_availability_check()
891+
892+ def _livepatch_enabled_changed_cb(self, fm, file, other_file, event_type):
893+ enabled = file.query_exists()
894+ if self._enabled != enabled:
895+ self._enabled = enabled
896+ self.notify('enabled')
897
898=== added file 'softwareproperties/LivepatchSnap.py'
899--- softwareproperties/LivepatchSnap.py 1970-01-01 00:00:00 +0000
900+++ softwareproperties/LivepatchSnap.py 2019-02-18 16:16:34 +0000
901@@ -0,0 +1,135 @@
902+#
903+# Copyright (c) 2019 Canonical
904+#
905+# Authors:
906+# Andrea Azzarone <andrea.azzarone@canonical.com>
907+#
908+# This program is free software; you can redistribute it and/or
909+# modify it under the terms of the GNU General Public License as
910+# published by the Free Software Foundation; either version 2 of the
911+# License, or (at your option) any later version.
912+#
913+# This program is distributed in the hope that it will be useful,
914+# but WITHOUT ANY WARRANTY; without even the implied warranty of
915+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
916+# GNU General Public License for more details.
917+#
918+# You should have received a copy of the GNU General Public License
919+# along with this program; if not, write to the Free Software
920+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
921+# USA
922+
923+from gettext import gettext as _
924+import logging
925+
926+import gi
927+from gi.repository import Gio, GLib
928+
929+try:
930+ gi.require_version('Snapd', '1')
931+ from gi.repository import Snapd
932+except(ImportError, ValueError):
933+ pass
934+
935+
936+class LivepatchSnap(object):
937+
938+ # Constants
939+ SNAP_NAME = 'canonical-livepatch'
940+
941+ # Public API
942+ def __init__(self):
943+ self._snapd_client = Snapd.Client()
944+ self._cancellable = Gio.Cancellable()
945+
946+ def get_status(self):
947+ """ Get the status of canonical-livepatch snap.
948+
949+ Returns:
950+ Snapd.SnapStatus.Enun: An enum indicating the status of the snap.
951+ """
952+ snap = self._get_raw_snap()
953+ return snap.get_status() if snap else Snapd.SnapStatus.UNKNOWN
954+
955+ def enable_or_install(self):
956+ """Enable or install canonical-livepatch snap.
957+
958+ Returns:
959+ (True, '') if successful, (False, error_message) otherwise.
960+ """
961+ status = self.get_status()
962+
963+ if status == Snapd.SnapStatus.ACTIVE:
964+ logging.warning('{} snap is already active'.format(self.SNAP_NAME))
965+ return True, ''
966+ elif status == Snapd.SnapStatus.AVAILABLE:
967+ return self._install()
968+ elif status == Snapd.SnapStatus.INSTALLED:
969+ return self._enable()
970+ else:
971+ logging.warning('{} snap is in an unknown state'.format(self.SNAP_NAME))
972+ return False, _('Canonical Livepatch snap cannot be installed.')
973+
974+ # Private methods
975+ def _get_raw_snap(self):
976+ """Get the Sanpd.Snap raw object of the canonical-livepatch snapd.
977+
978+ Returns:
979+ Sanpd.Snap if successful, None otherwise.
980+ """
981+ try:
982+ snap = self._snapd_client.get_snap_sync(
983+ name=self.SNAP_NAME,
984+ cancellable=self._cancellable)
985+ except GLib.Error as e:
986+ logging.debug('Snapd.Client.get_snap_sync failed: {}'.format(e.message))
987+ snap = None
988+
989+ if snap:
990+ return snap
991+
992+ try:
993+ (snaps, ignored) = self._snapd_client.find_sync(
994+ flags=Snapd.FindFlags.MATCH_NAME,
995+ query=self.SNAP_NAME,
996+ cancellable=self._cancellable)
997+ snap = snaps[0]
998+ except GLib.Error as e:
999+ logging.debug('Snapd.Client.find_sync failed: {}'.format(e.message))
1000+
1001+ return snap
1002+
1003+ def _install(self):
1004+ """Install canonical-livepatch snap.
1005+
1006+ Returns:
1007+ (True, '') if successful, (False, error_message) otherwise.
1008+ """
1009+ assert self.get_status() == Snapd.SnapStatus.AVAILABLE
1010+
1011+ try:
1012+ self._snapd_client.install2_sync(
1013+ flags=Snapd.InstallFlags.NONE,
1014+ name=self.SNAP_NAME,
1015+ cancellable=self._cancellable)
1016+ except GLib.Error as e:
1017+ return False, _('Canonical Livepatch snap cannot be installed: {}'.format(e.message))
1018+ else:
1019+ return True, ''
1020+
1021+ def _enable(self):
1022+ """Enable the canonical-livepatch snap.
1023+
1024+ Returns:
1025+ (True, '') if successful, (False, error_message) otherwise.
1026+ """
1027+ assert self.get_status() == Snapd.SnapStatus.INSTALLED
1028+
1029+ try:
1030+ self._snapd_client.enable_sync(
1031+ name=self.SNAP_NAME,
1032+ cancellable=self._cancellable)
1033+ except GLib.Error as e:
1034+ return False, _('Canonical Livepatch snap cannot be enabled: {}'.format(e.message))
1035+ else:
1036+ return True, ''
1037
1038=== modified file 'softwareproperties/SoftwareProperties.py'
1039--- softwareproperties/SoftwareProperties.py 2018-12-18 13:46:40 +0000
1040+++ softwareproperties/SoftwareProperties.py 2019-02-18 16:16:34 +0000
1041@@ -32,7 +32,6 @@
1042 import os
1043 import glob
1044 import shutil
1045-import subprocess
1046 import threading
1047 import atexit
1048 import tempfile
1049@@ -65,15 +64,8 @@
1050 from . import ppa
1051 from . import cloudarchive
1052
1053-import gi
1054 from gi.repository import Gio
1055
1056-try:
1057- gi.require_version('Snapd', '1')
1058- from gi.repository import Snapd
1059-except (ImportError, ValueError):
1060- pass
1061-
1062 _SHORTCUT_FACTORIES = [
1063 ppa.shortcut_handler,
1064 cloudarchive.shortcut_handler,
1065@@ -100,9 +92,6 @@
1066 RELEASE_UPGRADES_NEVER : 'never',
1067 }
1068
1069- # file to monitor canonical-livepatch status
1070- LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
1071-
1072 def __init__(self, datadir=None, options=None, rootdir="/"):
1073 """ Provides the core functionality to configure the used software
1074 repositories, the corresponding authentication keys and
1075@@ -871,147 +860,6 @@
1076 except:
1077 return False
1078
1079- #
1080- # Livepatch
1081- #
1082- def init_snapd(self):
1083- self.snapd_client = Snapd.Client()
1084-
1085- def get_livepatch_snap_async(self, callback):
1086- assert self.snapd_client
1087- self.snapd_client.list_one_async('canonical-livepatch',
1088- self.cancellable,
1089- self.on_list_one_ready_cb,
1090- callback)
1091-
1092- def on_list_one_ready_cb(self, source_object, result, user_data):
1093- callback = user_data
1094- try:
1095- snap = source_object.list_one_finish(result)
1096- except:
1097- snap = None
1098- if snap:
1099- if callback:
1100- callback(snap)
1101- return
1102- else:
1103- assert self.snapd_client
1104- self.snapd_client.find_async(Snapd.FindFlags.MATCH_NAME,
1105- 'canonical-livepatch',
1106- self.cancellable,
1107- self.on_find_ready_cb,
1108- callback)
1109-
1110- def on_find_ready_cb(self, source_object, result, user_data):
1111- callback = user_data
1112- try:
1113- snaps = source_object.find_finish(result)[0]
1114- except:
1115- snaps = list()
1116- snap = snaps[0] if len(snaps) else None
1117- if callback:
1118- callback(snap)
1119-
1120- def get_livepatch_snap_status(self, snap):
1121- if snap is None:
1122- return Snapd.SnapStatus.UNKNOWN
1123- return snap.get_status()
1124-
1125- # glib-snapd does not keep track of the status of the snap. Use this decorator
1126- # to make it easy to write async functions that will always have an updated
1127- # snap object.
1128- def require_livepatch_snap(func):
1129- def get_livepatch_snap_and_call(*args, **kwargs):
1130- return args[0].get_livepatch_snap_async(lambda snap: func(snap=snap, *args, **kwargs))
1131- return get_livepatch_snap_and_call
1132-
1133- def is_livepatch_enabled(self):
1134- file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
1135- return file.query_exists(None)
1136-
1137- @require_livepatch_snap
1138- def set_livepatch_enabled_async(self, enabled, token, callback, snap=None):
1139- status = self.get_livepatch_snap_status(snap)
1140- if status == Snapd.SnapStatus.UNKNOWN:
1141- if callback:
1142- callback(True, _("Canonical Livepatch snap cannot be installed."))
1143- elif status == Snapd.SnapStatus.ACTIVE:
1144- if enabled:
1145- error = self.enable_livepatch_service(token)
1146- else:
1147- error = self.disable_livepatch_service()
1148- if callback:
1149- callback(len(error) > 0, error)
1150- elif status == Snapd.SnapStatus.INSTALLED:
1151- if enabled:
1152- self.snapd_client.enable_async(name='canonical-livepatch',
1153- cancellable=self.cancellable,
1154- callback=self.livepatch_enable_snap_cb,
1155- user_data=(callback, token))
1156- else:
1157- if callback:
1158- callback(False, "")
1159- elif status == Snapd.SnapStatus.AVAILABLE:
1160- if enabled:
1161- self.snapd_client.install_async(name='canonical-livepatch',
1162- cancellable=self.cancellable,
1163- callback=self.livepatch_install_snap_cb,
1164- user_data=(callback, token))
1165- else:
1166- if callback:
1167- callback(False, "")
1168-
1169- def livepatch_enable_snap_cb(self, source_object, result, user_data):
1170- (callback, token) = user_data
1171- try:
1172- if source_object.enable_finish(result):
1173- error = self.enable_livepatch_service(token)
1174- if callback:
1175- callback(len(error) > 0, error)
1176- except Exception:
1177- if callback:
1178- callback(True, _("Canonical Livepatch snap cannot be enabled."))
1179-
1180- def livepatch_install_snap_cb(self, source_object, result, user_data):
1181- (callback, token) = user_data
1182- try:
1183- if source_object.install_finish(result):
1184- error = self.enable_livepatch_service(token)
1185- if callback:
1186- callback(len(error) > 0, error)
1187- except Exception:
1188- if callback:
1189- callback(True, _("Canonical Livepatch snap cannot be installed."))
1190-
1191- def enable_livepatch_service(self, token):
1192- generic_error = _("Canonical Livepatch cannot be enabled.")
1193-
1194- if self.is_livepatch_enabled():
1195- return ""
1196-
1197- try:
1198- subprocess.check_output(['/snap/bin/canonical-livepatch', 'enable', token], stderr=subprocess.STDOUT)
1199- return ""
1200- except subprocess.CalledProcessError as e:
1201- return e.output if e.output else generic_error
1202- except:
1203- return generic_error
1204-
1205-
1206- def disable_livepatch_service(self):
1207- generic_error = _("Canonical Livepatch cannot be disabled.")
1208-
1209- if not self.is_livepatch_enabled():
1210- return ""
1211-
1212- try:
1213- subprocess.check_output(['/snap/bin/canonical-livepatch', 'disable'], stderr=subprocess.STDOUT)
1214- return ""
1215- except subprocess.CalledProcessError as e:
1216- return e.output if e.output else generic_error
1217- except:
1218- return generic_error
1219-
1220 def shortcut_handler(shortcut):
1221 for factory in _SHORTCUT_FACTORIES:
1222 ret = factory(shortcut)
1223
1224=== modified file 'softwareproperties/dbus/SoftwarePropertiesDBus.py'
1225--- softwareproperties/dbus/SoftwarePropertiesDBus.py 2019-01-12 13:06:12 +0000
1226+++ softwareproperties/dbus/SoftwarePropertiesDBus.py 2019-02-18 16:16:34 +0000
1227@@ -25,10 +25,12 @@
1228 import subprocess
1229 import tempfile
1230 import sys
1231+import threading
1232
1233 from aptsources.sourceslist import SourceEntry
1234
1235 from dbus.mainloop.glib import DBusGMainLoop
1236+from softwareproperties.LivepatchService import LivepatchService
1237 from softwareproperties.SoftwareProperties import SoftwareProperties
1238
1239 DBUS_BUS_NAME = 'com.ubuntu.SoftwareProperties'
1240@@ -61,7 +63,7 @@
1241 self.enforce_polkit = True
1242 logging.debug("waiting for connections")
1243
1244- self.init_snapd()
1245+ self._livepatch_service = LivepatchService()
1246
1247 # override set_modified_sourceslist to emit a signal
1248 def save_sourceslist(self):
1249@@ -336,9 +338,13 @@
1250 sender_keyword="sender", connection_keyword="conn",
1251 in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler'))
1252 def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None):
1253+ def enable_thread_func():
1254+ ret = self._livepatch_service.set_enabled(enabled, token)
1255+ GLib.idle_add(lambda: reply_handler(*ret))
1256+
1257 self._check_policykit_privilege(
1258 sender, conn, "com.ubuntu.softwareproperties.applychanges")
1259- self.set_livepatch_enabled_async(enabled, token, reply_handler)
1260+ threading.Thread(target=enable_thread_func).start()
1261
1262 # helper from jockey
1263 def _check_policykit_privilege(self, sender, conn, privilege):
1264
1265=== modified file 'softwareproperties/gtk/DialogLivepatchError.py'
1266--- softwareproperties/gtk/DialogLivepatchError.py 2018-03-21 14:49:38 +0000
1267+++ softwareproperties/gtk/DialogLivepatchError.py 2019-02-18 16:16:34 +0000
1268@@ -1,5 +1,5 @@
1269 #
1270-# Copyright (c) 2017-2018 Canonical
1271+# Copyright (c) 2017-2019 Canonical
1272 #
1273 # Authors:
1274 # Andrea Azzarone <andrea.azzarone@canonical.com>
1275@@ -21,6 +21,8 @@
1276
1277 import os
1278
1279+from gettext import gettext as _
1280+
1281 from softwareproperties.gtk.utils import (
1282 setup_ui,
1283 )
1284@@ -31,20 +33,27 @@
1285 RESPONSE_SETTINGS = 100
1286 RESPONSE_IGNORE = 101
1287
1288+ primary = _("Sorry, there's been a problem with setting up Canonical Livepatch.")
1289+
1290 def __init__(self, parent, datadir):
1291 """setup up the gtk dialog"""
1292 self.parent = parent
1293
1294- setup_ui(self, os.path.join(datadir, "gtkbuilder",
1295- "dialog-livepatch-error.ui"), domain="software-properties")
1296+ setup_ui(
1297+ self,
1298+ os.path.join(datadir, "gtkbuilder", "dialog-livepatch-error.ui"),
1299+ domain="software-properties")
1300
1301 self.dialog = self.messagedialog_livepatch
1302- self.dialog.use_header_bar = True
1303 self.dialog.set_transient_for(parent)
1304
1305 def run(self, error, show_settings_button):
1306- self.dialog.format_secondary_markup(
1307- "The error was: \"%s\"" % error.strip())
1308+ p = "<span weight=\"bold\" size=\"larger\">{}</span>".format(self.primary)
1309+ self.label_primary.set_markup(p)
1310+
1311+ textbuffer = self.treeview_message.get_buffer()
1312+ textbuffer.set_text(error)
1313+
1314 self.button_settings.set_visible(show_settings_button)
1315 res = self.dialog.run()
1316 self.dialog.hide()
1317
1318=== added file 'softwareproperties/gtk/LivepatchPage.py'
1319--- softwareproperties/gtk/LivepatchPage.py 1970-01-01 00:00:00 +0000
1320+++ softwareproperties/gtk/LivepatchPage.py 2019-02-18 16:16:34 +0000
1321@@ -0,0 +1,375 @@
1322+#
1323+# Copyright (c) 2019 Canonical
1324+#
1325+# Authors:
1326+# Andrea Azzarone <andrea.azzarone@canonical.com>
1327+#
1328+# This program is free software; you can redistribute it and/or
1329+# modify it under the terms of the GNU General Public License as
1330+# published by the Free Software Foundation; either version 2 of the
1331+# License, or (at your option) any later version.
1332+#
1333+# This program is distributed in the hope that it will be useful,
1334+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1335+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1336+# GNU General Public License for more details.
1337+#
1338+# You should have received a copy of the GNU General Public License
1339+# along with this program; if not, write to the Free Software
1340+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
1341+# USA
1342+
1343+import datetime
1344+import gettext
1345+from gettext import gettext as _
1346+import gi
1347+gi.require_version("Gtk", "3.0")
1348+from gi.repository import GLib, GObject, Gtk
1349+import logging
1350+
1351+from softwareproperties.GoaAuth import GoaAuth
1352+from softwareproperties.LivepatchService import (
1353+ LivepatchService,
1354+ LivepatchAvailability)
1355+from .DialogAuth import DialogAuth
1356+from .DialogLivepatchError import DialogLivepatchError
1357+
1358+
1359+class LivepatchPage(object):
1360+
1361+ # Constants
1362+ COMMON_ISSUE_URL = 'https://wiki.ubuntu.com/Kernel/Livepatch#CommonIssues'
1363+ GENERIC_ERR_MSG = _('Canonical Livepatch has experienced an internal error.'
1364+ ' Please refer to {} for further information.'.format(COMMON_ISSUE_URL))
1365+
1366+ def __init__(self, parent):
1367+ self._parent = parent
1368+
1369+ self._timeout_handler = -1
1370+ self._waiting_livepatch_response = False
1371+
1372+ self._lps = LivepatchService()
1373+ self._auth = GoaAuth()
1374+
1375+ # Connect signals
1376+ self._lps.connect(
1377+ 'notify::availability', self._lps_availability_changed_cb)
1378+ self._lps.connect(
1379+ 'notify::enabled', self._lps_enabled_changed_cb)
1380+ self._auth.connect(
1381+ 'notify', self._auth_changed_cb)
1382+ self._state_set_handler = self._parent.switch_livepatch.connect(
1383+ 'state-set', self._switch_state_set_cb)
1384+ self._parent.button_livepatch_login.connect(
1385+ 'clicked', self._button_livepatch_login_clicked_cb)
1386+
1387+ self._lps.trigger_availability_check()
1388+
1389+ @property
1390+ def waiting_livepatch_response(self):
1391+ return self._waiting_livepatch_response
1392+
1393+ # Private methods
1394+ def _trigger_ui_update(self, skip=False, error_message=None):
1395+ """Trigger the update of every single user interface component according
1396+ to the current state.
1397+
1398+ Args:
1399+ skip (bool): whether to trigger the update after a small timeout.
1400+ Defaults to False.
1401+ error_message (str): error message to display. Defaults to None.
1402+ """
1403+ def do_ui_update():
1404+ self._timeout_handler = -1
1405+
1406+ self._update_switch()
1407+ self._update_spinner()
1408+ self._update_switch_label()
1409+ self._update_auth_button()
1410+ self._update_stack(error_message)
1411+
1412+ return False
1413+
1414+ if self._timeout_handler > 0:
1415+ GObject.source_remove(self._timeout_handler)
1416+ self._timeout_handler = -1
1417+
1418+ if skip:
1419+ do_ui_update()
1420+ else:
1421+ self._timeout_handler = GLib.timeout_add_seconds(2, do_ui_update)
1422+
1423+ def _update_switch(self):
1424+ """Update the state of the on/off switch."""
1425+ switch = self._parent.switch_livepatch
1426+
1427+ availability = self._lps.props.availability
1428+ enabled = self._lps.props.enabled
1429+ logged = self._auth.logged
1430+
1431+ switch.set_sensitive(
1432+ availability == LivepatchAvailability.TRUE and
1433+ (enabled or logged))
1434+
1435+ if self._waiting_livepatch_response:
1436+ return
1437+
1438+ self._parent.switch_livepatch.handler_block(self._state_set_handler)
1439+ switch.set_state(switch.get_sensitive() and enabled)
1440+ self._parent.switch_livepatch.handler_unblock(self._state_set_handler)
1441+
1442+ def _update_spinner(self):
1443+ """Update the state of the in-progress spinner."""
1444+ spinner = self._parent.spinner_livepatch
1445+ availability = self._lps.props.availability
1446+
1447+ spinner.set_visible(availability == LivepatchAvailability.CHECKING)
1448+ spinner.props.active = (availability == LivepatchAvailability.CHECKING)
1449+
1450+ def _update_switch_label(self):
1451+ """Update the text of the label next to the on/off switch."""
1452+ availability = self._lps.props.availability
1453+ logged = self._auth.logged
1454+
1455+ if availability == LivepatchAvailability.CHECKING:
1456+ msg = _('Checking availability…')
1457+ elif availability == LivepatchAvailability.NO_CONNECTIVITY:
1458+ msg = _('Livepatch requires an Internet connection.')
1459+ elif availability == LivepatchAvailability.FALSE:
1460+ msg = _('Livepatch is not available for this system.')
1461+ else:
1462+ if self._parent.switch_livepatch.get_active():
1463+ msg = _("Livepatch is on.")
1464+ elif not logged:
1465+ msg = _("To use Livepatch you need to sign in.")
1466+ else:
1467+ msg = _("Livepatch is off.")
1468+
1469+ self._parent.label_livepatch_switch.set_label(msg)
1470+
1471+ def _update_auth_button(self):
1472+ """Update the state and the label of the authentication button."""
1473+ button = self._parent.button_livepatch_login
1474+
1475+ availability = self._lps.props.availability
1476+ logged = self._auth.logged
1477+
1478+ button.set_visible(
1479+ availability == LivepatchAvailability.TRUE and
1480+ not self._parent.switch_livepatch.get_active())
1481+ button.set_label(_('Sign Out') if logged else _('Sign In…'))
1482+
1483+ def _update_stack(self, error_message):
1484+ """Update the state of the stack.
1485+
1486+ If livepatch is not available nothing will be shown, if an error
1487+ occurred an error message will be shown in a text view, otherwise the
1488+ current livepatch status (e.g. a list of CVE fixes) will be shown.
1489+
1490+ Args:
1491+ error_message (str): error message to display.
1492+ """
1493+ availability = self._lps.props.availability
1494+ availability_message = self._lps.props.availability_message
1495+
1496+ has_error = (
1497+ error_message or
1498+ (availability == LivepatchAvailability.FALSE and
1499+ availability_message is not None))
1500+
1501+ if has_error:
1502+ self._parent.stack_livepatch.set_visible(True)
1503+ self._parent.stack_livepatch.set_visible_child_name('page_livepatch_message')
1504+ text_buffer = self._parent.textview_livepatch.get_buffer()
1505+ text_buffer.delete(
1506+ text_buffer.get_start_iter(), text_buffer.get_end_iter())
1507+ text_buffer.insert_markup(
1508+ text_buffer.get_end_iter(),
1509+ error_message or availability_message, -1)
1510+ return
1511+
1512+ if availability == LivepatchAvailability.CHECKING or not self._parent.switch_livepatch.get_active():
1513+ self._parent.stack_livepatch.set_visible(False)
1514+ else:
1515+ self._update_status()
1516+
1517+ def _format_timedelta(self, td):
1518+ days = td.days
1519+ hours = td.seconds // 3600
1520+ minutes = td.seconds // 60
1521+
1522+ if days > 0:
1523+ return gettext.ngettext(
1524+ '({} day ago)',
1525+ '({} days ago)',
1526+ days).format(days)
1527+ elif hours > 0:
1528+ return gettext.ngettext(
1529+ '({} hour ago)',
1530+ '({} hours ago)',
1531+ hours).format(hours)
1532+ elif minutes > 0:
1533+ return gettext.ngettext(
1534+ '({} minute ago)',
1535+ '({} minutes ago)',
1536+ minutes).format(minutes)
1537+ else:
1538+ return ''
1539+
1540+ def _datetime_to_str(self, dt):
1541+ gdt = GLib.DateTime.new_from_unix_utc(dt.timestamp())
1542+ td = datetime.datetime.now(dt.tzinfo) - dt
1543+ return '{} {}'.format(
1544+ gdt.to_local().format('%x %H:%M'),
1545+ self._format_timedelta(td))
1546+
1547+ def _update_status(self):
1548+ """Populate the UI to reflect the Livepatch status"""
1549+ status = self._lps.get_status()
1550+
1551+ if status is None:
1552+ if not self._waiting_livepatch_response:
1553+ self._trigger_ui_update(skip=True, error_message=_('Failed to retrieve Livepatch status.'))
1554+ return
1555+
1556+ self._parent.stack_livepatch.set_visible(True)
1557+ self._parent.stack_livepatch.set_visible_child_name('page_livepatch_status')
1558+
1559+ check_state = status['Status'][0]['Livepatch']['CheckState'] if status else None
1560+ state = status['Status'][0]['Livepatch']['State'] if status else None
1561+
1562+ if check_state == 'check-failed':
1563+ self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG)
1564+ return
1565+
1566+ if state in ['applied-with-bug', 'apply-failed', 'unknown']:
1567+ self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG)
1568+ return
1569+
1570+ self._parent.label_livepatch_last_update.set_label(
1571+ _("Last check for updates: {}").format(
1572+ self._datetime_to_str(status['Last-Check']) if status else _('None yet')))
1573+
1574+ if state in ['unapplied', 'nothing-to-apply'] or state == None:
1575+ self._parent.label_livepatch_header.set_label(_('No updates currently applied.'))
1576+ self._parent.scrolledwindow_livepatch_fixes.set_visible(False)
1577+ elif state == 'applied':
1578+ self._parent.label_livepatch_header.set_label(_('Updates currently applied:'))
1579+ self._parent.scrolledwindow_livepatch_fixes.set_visible(True)
1580+ self._update_fixes(status['Status'][0]['Livepatch']['Fixes'])
1581+ else:
1582+ logging.warning('Livepatch status contains an invalid state: {}'.format(state))
1583+
1584+ if check_state == 'needs-check' or state == 'unapplied':
1585+ self._trigger_ui_update()
1586+
1587+ def _update_fixes(self, fixes):
1588+ """Populate the UI to show the list of applied CVE fixes."""
1589+ treeview = self._parent.treeview_livepatch
1590+ liststore = treeview.get_model()
1591+ liststore.clear()
1592+
1593+ for fix in fixes:
1594+ fix_iter = liststore.append()
1595+ liststore.set(fix_iter, [0], [self._format_fix(fix)])
1596+
1597+ def _format_fix(self, fix):
1598+ """Format a fix in a UI friendly text."""
1599+ return '<b>{}</b>\n{}'.format(
1600+ fix['Name'], fix['Description'].replace('\n', ' '))
1601+
1602+ def _do_login(self):
1603+ """Start the authentication flow to retrieve the livepatch token."""
1604+ dialog = DialogAuth(self._parent.window_main, self._parent.datadir)
1605+
1606+ if dialog.run() == Gtk.ResponseType.OK:
1607+ self._auth.login(dialog.account)
1608+ self._parent.switch_livepatch.set_state(True)
1609+
1610+ def _do_logout(self):
1611+ """Start the de-authentication flow."""
1612+ self._auth.logout()
1613+
1614+ # Signals handler
1615+ def _lps_availability_changed_cb(self, o, v):
1616+ self._trigger_ui_update(skip=True)
1617+
1618+ def _lps_enabled_changed_cb(self, o, v):
1619+ if self._waiting_livepatch_response:
1620+ return
1621+ self._trigger_ui_update(skip=False)
1622+
1623+ def _auth_changed_cb(self, o, v):
1624+ self._trigger_ui_update(skip=True)
1625+
1626+ def _switch_state_set_cb(self, widget, state):
1627+ if not self._waiting_livepatch_response:
1628+ self._waiting_livepatch_response = True
1629+
1630+ token = self._auth.token or ''
1631+ self._parent.backend.SetLivepatchEnabled(
1632+ state, token,
1633+ reply_handler=self._enabled_reply_handler,
1634+ error_handler=self._enabled_error_handler,
1635+ timeout=1200)
1636+
1637+ self._trigger_ui_update(skip=True)
1638+ self._parent.switch_livepatch.set_state(state)
1639+
1640+ return False
1641+
1642+ def _button_livepatch_login_clicked_cb(self, button):
1643+ if self._auth.logged:
1644+ self._do_logout()
1645+ else:
1646+ self._do_login()
1647+
1648+ def _show_error_dialog(self, message):
1649+ dialog = DialogLivepatchError(
1650+ self._parent.window_main,
1651+ self._parent.datadir)
1652+
1653+ response = dialog.run(
1654+ error=message,
1655+ show_settings_button=not self._parent.window_main.is_visible())
1656+
1657+ if response == DialogLivepatchError.RESPONSE_SETTINGS:
1658+ self._parent.window_main.show()
1659+ self._parent.notebook_main.set_current_page(6)
1660+ elif not self._parent.window_main.is_visible():
1661+ self._parent.on_close_button(None)
1662+
1663+ # DBus replay handlers
1664+ def _enabled_reply_handler(self, is_error, prompt):
1665+ if self._parent.switch_livepatch.get_active() == self._lps.props.enabled:
1666+ self._waiting_livepatch_response = False
1667+ self._trigger_ui_update(skip=True)
1668+
1669+ if not self._parent.window_main.is_visible():
1670+ self._parent.on_close_button(None)
1671+ else:
1672+ if is_error:
1673+ self._waiting_livepatch_response = False
1674+ self._trigger_ui_update(skip=True)
1675+ self._show_error_dialog(prompt)
1676+ else:
1677+ # The user tooggled on/off the switch while we were waiting
1678+ # livepatch to respond back.
1679+ self._parent.backend.SetLivepatchEnabled(
1680+ self._parent.switch_livepatch.get_active(),
1681+ self._auth.token,
1682+ reply_handler=self._enabled_reply_handler,
1683+ error_handler=self._enabled_error_handler,
1684+ timeout=1200)
1685+
1686+ def _enabled_error_handler(self, e):
1687+ self._waiting_livepatch_response = False
1688+ self._trigger_ui_update(skip=True)
1689+
1690+ if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1691+ logging.warning("Authentication canceled, changes have not been saved")
1692+
1693+ if not self._parent.window_main.is_visible():
1694+ self._parent.on_close_button(None)
1695+ else:
1696+ self._show_error_dialog(str(e))
1697
1698=== modified file 'softwareproperties/gtk/SimpleGtkbuilderApp.py'
1699--- softwareproperties/gtk/SimpleGtkbuilderApp.py 2016-09-09 07:09:03 +0000
1700+++ softwareproperties/gtk/SimpleGtkbuilderApp.py 2019-02-18 16:16:34 +0000
1701@@ -38,6 +38,9 @@
1702 def on_activate(self, data=None):
1703 self.add_window(self.window_main)
1704
1705+ if not self.window_main.is_visible():
1706+ self.window_main.show()
1707+
1708 def run(self):
1709 """
1710 Starts the main loop of processing events checking for Control-C.
1711
1712=== modified file 'softwareproperties/gtk/SoftwarePropertiesGtk.py'
1713--- softwareproperties/gtk/SoftwarePropertiesGtk.py 2018-12-18 13:46:40 +0000
1714+++ softwareproperties/gtk/SoftwarePropertiesGtk.py 2019-02-18 16:16:34 +0000
1715@@ -27,9 +27,6 @@
1716
1717 import apt
1718 import apt_pkg
1719-import aptsources.distro
1720-from datetime import datetime
1721-import distro_info
1722 import dbus
1723 from gettext import gettext as _
1724 import gettext
1725@@ -52,11 +49,9 @@
1726 from .DialogEdit import DialogEdit
1727 from .DialogCacheOutdated import DialogCacheOutdated
1728 from .DialogAddSourcesList import DialogAddSourcesList
1729-from .DialogLivepatchError import DialogLivepatchError
1730-from .DialogAuth import DialogAuth
1731+from .LivepatchPage import LivepatchPage
1732
1733 import softwareproperties
1734-from softwareproperties.GoaAuth import GoaAuth
1735 import softwareproperties.distro
1736 from softwareproperties.SoftwareProperties import SoftwareProperties
1737 import softwareproperties.SoftwareProperties
1738@@ -85,8 +80,6 @@
1739 STORE_VISIBLE
1740 ) = list(range(5))
1741
1742-LIVEPATCH_TIMEOUT = 1200
1743-
1744
1745 def error(parent_window, summary, msg):
1746 """ show a error dialog """
1747@@ -1045,7 +1038,9 @@
1748 d = DialogCacheOutdated(self.window_main,
1749 self.datadir)
1750 d.run()
1751- if self.waiting_livepatch_response:
1752+
1753+ self.quit_when_livepatch_responds = False
1754+ if self.livepatch_page.waiting_livepatch_response:
1755 self.quit_when_livepatch_responds = True
1756 self.hide()
1757 else:
1758@@ -1483,164 +1478,6 @@
1759 else:
1760 self.label_driver_action.set_label(_("No proprietary drivers are in use."))
1761
1762- #
1763- # Livepatch
1764- #
1765 def init_livepatch(self):
1766- self.goa_auth = GoaAuth()
1767- self.waiting_livepatch_response = False
1768- self.quit_when_livepatch_responds = False
1769-
1770- if not self.is_livepatch_supported():
1771- self.grid_livepatch.set_visible(False)
1772- return
1773-
1774- self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1775- self.on_goa_auth_changed()
1776-
1777- # hacky way to monitor if livepatch is enabled or not
1778- file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
1779- self.lp_monitor = file.monitor_file(Gio.FileMonitorFlags.NONE)
1780-
1781- # connect to signals
1782- self.handlers[self.goa_auth] = \
1783- self.goa_auth.connect('notify', lambda o, p: self.on_goa_auth_changed())
1784- self.handlers[self.checkbutton_livepatch] = \
1785- self.checkbutton_livepatch.connect('toggled', self.on_checkbutton_livepatch_toggled)
1786- self.handlers[self.button_ubuntuone] = \
1787- self.button_ubuntuone.connect('clicked', self.on_button_ubuntuone_clicked)
1788- self.handlers[self.lp_monitor] = \
1789- self.lp_monitor.connect('changed', self.on_livepatch_status_changed)
1790-
1791- def has_online_accounts(self):
1792- try:
1793- d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop')
1794- return d != None
1795- except Exception:
1796- return False
1797-
1798- def is_livepatch_supported(self):
1799- distro = aptsources.distro.get_distro()
1800- di = distro_info.UbuntuDistroInfo()
1801-
1802- return self.has_online_accounts() and \
1803- di.is_lts(distro.codename) and \
1804- distro.codename in di.supported(datetime.now().date())
1805-
1806- def on_goa_auth_changed(self):
1807- if self.goa_auth.logged:
1808- self.button_ubuntuone.set_label(_('Sign Out'))
1809-
1810- if self.goa_auth.token:
1811- self.checkbutton_livepatch.set_sensitive(True)
1812- self.label_livepatch_login.set_label(_('Signed in as %s' % self.goa_auth.username))
1813- else:
1814- self.checkbutton_livepatch.set_sensitive(False)
1815- text = _('%s isn\'t authorized to use Livepatch.' % self.goa_auth.username)
1816- text = "<span color='red'>" + text + "</span>"
1817- self.label_livepatch_login.set_markup(text)
1818- else:
1819- if self.is_livepatch_enabled() and not self.waiting_livepatch_response:
1820- # Allow the user to disable livepatch even if
1821- # the account expired (see LP: #1768797)
1822- self.checkbutton_livepatch.set_sensitive(True)
1823- self.label_livepatch_login.set_label(_('Livepatch is active.'))
1824- else:
1825- self.checkbutton_livepatch.set_sensitive(False)
1826- self.label_livepatch_login.set_label(_('To use Livepatch you need to sign in.'))
1827-
1828- self.button_ubuntuone.set_label(_('Sign In…'))
1829-
1830- def on_livepatch_status_changed(self, file_monitor, file, other_file, event_type):
1831- if not self.waiting_livepatch_response:
1832- self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1833- self.on_goa_auth_changed()
1834-
1835- def on_button_ubuntuone_clicked(self, button):
1836- if self.goa_auth.logged:
1837- self.do_logout()
1838- else:
1839- self.do_login()
1840-
1841- def do_login(self):
1842- try:
1843- # Show login dialog!
1844- dialog = DialogAuth(self.window_main, self.datadir)
1845- response = dialog.run()
1846- except Exception as e:
1847- logging.error(e)
1848- error(self.window_main,
1849- _("Error enabling Canonical Livepatch"),
1850- _("Please check your Internet connection."))
1851- else:
1852- if response == Gtk.ResponseType.OK:
1853- self.goa_auth.login(dialog.account)
1854- if self.goa_auth.logged:
1855- self.checkbutton_livepatch.set_active(True)
1856-
1857- def do_logout(self):
1858- self.checkbutton_livepatch.set_active(False)
1859- self.goa_auth.logout()
1860-
1861- def on_checkbutton_livepatch_toggled(self, checkbutton):
1862- if self.waiting_livepatch_response:
1863- return
1864-
1865- self.waiting_livepatch_response = True
1866-
1867- token = ''
1868- enabled = False
1869- if self.checkbutton_livepatch.get_active():
1870- enabled = True
1871- token = self.goa_auth.token if self.goa_auth.token else ''
1872- self.backend.SetLivepatchEnabled(enabled, token,
1873- reply_handler=self.livepatch_enabled_reply_handler,
1874- error_handler=self.livepatch_enabled_error_handler,
1875- timeout=LIVEPATCH_TIMEOUT)
1876-
1877- def livepatch_enabled_reply_handler(self, is_error, prompt):
1878- self.sync_checkbutton_livepatch(is_error, prompt)
1879-
1880- def livepatch_enabled_error_handler(self, e):
1881- if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1882- logging.error("Authentication canceled, changes have not been saved")
1883- self.sync_checkbutton_livepatch(is_error=True, prompt=None)
1884- else:
1885- self.sync_checkbutton_livepatch(is_error=True, prompt=str(e))
1886-
1887- def sync_checkbutton_livepatch(self, is_error, prompt):
1888- if is_error:
1889- self.waiting_livepatch_response = False
1890- self.checkbutton_livepatch.handler_block(self.handlers[self.checkbutton_livepatch])
1891- self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1892- self.checkbutton_livepatch.handler_unblock(self.handlers[self.checkbutton_livepatch])
1893-
1894- if prompt:
1895- dialog = DialogLivepatchError(self.window_main, self.datadir)
1896- response = dialog.run(prompt, show_settings_button=self.quit_when_livepatch_responds)
1897- if response == DialogLivepatchError.RESPONSE_SETTINGS:
1898- self.window_main.show()
1899- self.quit_when_livepatch_responds = False
1900- else:
1901- do_dbus_call = False
1902- if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active():
1903- do_dbus_call = True
1904- enabled = False
1905- token = ''
1906- elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active():
1907- do_dbus_call = True
1908- enabled = True
1909- token = self.goa_auth.token if self.goa_auth.token else ''
1910- else:
1911- self.waiting_livepatch_response = False
1912-
1913- if do_dbus_call:
1914- self.backend.SetLivepatchEnabled(enabled, token,
1915- reply_handler=self.livepatch_enabled_reply_handler,
1916- error_handler=self.livepatch_enabled_error_handler,
1917- timeout=LIVEPATCH_TIMEOUT)
1918-
1919- self.on_goa_auth_changed()
1920-
1921- if self.quit_when_livepatch_responds:
1922- self.on_close_button(self.button_close)
1923+ self.livepatch_page = LivepatchPage(self)
1924+
1925
1926=== modified file 'softwareproperties/gtk/utils.py'
1927--- softwareproperties/gtk/utils.py 2016-09-09 07:09:03 +0000
1928+++ softwareproperties/gtk/utils.py 2019-02-18 16:16:34 +0000
1929@@ -18,13 +18,19 @@
1930
1931 from __future__ import print_function
1932
1933+import aptsources.distro
1934+from datetime import datetime
1935+import distro_info
1936+from functools import wraps
1937 import gi
1938 gi.require_version("Gtk", "3.0")
1939-from gi.repository import Gtk
1940+from gi.repository import Gio, Gtk
1941
1942 import logging
1943 LOG=logging.getLogger(__name__)
1944
1945+import time
1946+
1947 def setup_ui(self, path, domain):
1948 # setup ui
1949 self.builder = Gtk.Builder()
1950@@ -37,3 +43,52 @@
1951 setattr(self, name, o)
1952 else:
1953 logging.debug("can not get name for object '%s'" % o)
1954+
1955+def has_gnome_online_accounts():
1956+ try:
1957+ d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop')
1958+ return d != None
1959+ except Exception:
1960+ return False
1961+
1962+def is_current_distro_lts():
1963+ distro = aptsources.distro.get_distro()
1964+ di = distro_info.UbuntuDistroInfo()
1965+ return di.is_lts(distro.codename)
1966+
1967+def is_current_distro_supported():
1968+ distro = aptsources.distro.get_distro()
1969+ di = distro_info.UbuntuDistroInfo()
1970+ return distro.codename in di.supported(datetime.now().date())
1971+
1972+def retry(exceptions, tries=10, delay=0.1, backoff=2):
1973+ """
1974+ Retry calling the decorated function using an exponential backoff.
1975+
1976+ Args:
1977+ exceptions: The exception to check. may be a tuple of
1978+ exceptions to check.
1979+ tries: Number of times to try (not retry) before giving up.
1980+ delay: Initial delay between retries in seconds.
1981+ backoff: Backoff multiplier (e.g. value of 2 will double the delay
1982+ each retry).
1983+ """
1984+ def deco_retry(f):
1985+
1986+ @wraps(f)
1987+ def f_retry(*args, **kwargs):
1988+ mtries, mdelay = tries, delay
1989+ while mtries > 1:
1990+ try:
1991+ return f(*args, **kwargs)
1992+ except exceptions as e:
1993+ msg = '{}, Retrying in {} seconds...'.format(e, mdelay)
1994+ logging.warning(msg)
1995+ time.sleep(mdelay)
1996+ mtries -= 1
1997+ mdelay *= backoff
1998+ return f(*args, **kwargs)
1999+
2000+ return f_retry # true decorator
2001+
2002+ return deco_retry

Subscribers

People subscribed via source and target branches

to status/vote changes: