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