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

Proposed by Andrea Azzarone on 2019-01-30
Status: Needs review
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 2019-01-30 Needs Fixing 32 minutes ago
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 on 2019-01-30

Add new files to po/POTFILES.in

1074. By Andrea Azzarone on 2019-01-30

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 on 2019-01-30

Update the generic error string.

1076. By Andrea Azzarone on 2019-02-07

Remove debug string.

1077. By Andrea Azzarone on 2019-02-07

Properly format the datetime of the last check.

1078. By Andrea Azzarone on 2019-02-07

Enable livepatch automatically after login.

1079. By Andrea Azzarone on 2019-02-07

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

1080. By Andrea Azzarone on 2019-02-07

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

Andrea Azzarone (azzar1) wrote :
1081. By Andrea Azzarone on 2019-02-07

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

1082. By Andrea Azzarone on 2019-02-07

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

Sebastien Bacher (seb128) wrote :

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

review: Needs Fixing
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 6 hours ago

Add missing comma.

1084. By Andrea Azzarone 6 hours ago

Get the attributes out of the translatable string.

1085. By Andrea Azzarone 6 hours ago

Make GtkScrolledWindow of textview_livepatch hidden by default.

1086. By Andrea Azzarone 6 hours ago

Make "Livepatch" string non transatable.

1087. By Andrea Azzarone 6 hours ago

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

1088. By Andrea Azzarone 6 hours ago

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

1089. By Andrea Azzarone 5 hours ago

Use logging.debug instead of logging.warning.

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!

Sebastien Bacher (seb128) wrote :

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

review: Needs Fixing
1090. By Andrea Azzarone 7 minutes ago

Don't make Livepatch tab name translatable.

Andrea Azzarone (azzar1) wrote :

Fixed.

Unmerged revisions

1090. By Andrea Azzarone 7 minutes ago

Don't make Livepatch tab name translatable.

1089. By Andrea Azzarone 5 hours ago

Use logging.debug instead of logging.warning.

1088. By Andrea Azzarone 6 hours ago

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

1087. By Andrea Azzarone 6 hours ago

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

1086. By Andrea Azzarone 6 hours ago

Make "Livepatch" string non transatable.

1085. By Andrea Azzarone 6 hours ago

Make GtkScrolledWindow of textview_livepatch hidden by default.

1084. By Andrea Azzarone 6 hours ago

Get the attributes out of the translatable string.

1083. By Andrea Azzarone 6 hours ago

Add missing comma.

1082. By Andrea Azzarone on 2019-02-07

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

1081. By Andrea Azzarone on 2019-02-07

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

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: