Merge lp:~azzar1/software-properties/livepatch-tab1 into lp:software-properties
- livepatch-tab1
- Merge into main
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 |
Related bugs: |
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:/
Please note that this does not include the checkbutton to enable/disable the livepatch indicator.
Description of the change
- 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.
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.
Sebastien Bacher (seb128) wrote : | # |
I didn't really review it yet, but one comment from a missing ',' which impacting the build
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:/
- 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!
- 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.
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:/
> 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!
Andrea Azzarone (azzar1) : | # |
Sebastien Bacher (seb128) wrote : | # |
Looks good now, you still have one translatable 'Livepatch' though
- 1090. By Andrea Azzarone
-
Don't make Livepatch tab name translatable.
Andrea Azzarone (azzar1) wrote : | # |
Fixed.
Preview Diff
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. <a href="https://www.ubuntu.com/livepatch">Learn More</a></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' |
508 | Binary 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' |
510 | Binary 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' |
512 | Binary 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' |
514 | Binary 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 |
Branch updated following design review: https:/ /wiki.ubuntu. com/SoftwareUpd ates?action= diff&rev2= 229&rev1= 228