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

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

Commit message

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

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

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

Add new files to po/POTFILES.in

1074. By Andrea Azzarone

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

1075. By Andrea Azzarone

Update the generic error string.

1076. By Andrea Azzarone

Remove debug string.

1077. By Andrea Azzarone

Properly format the datetime of the last check.

1078. By Andrea Azzarone

Enable livepatch automatically after login.

1079. By Andrea Azzarone

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

1080. By Andrea Azzarone

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

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

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

1082. By Andrea Azzarone

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

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

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

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

Ok, some extra inline comment, also

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

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

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

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

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

review: Needs Fixing
1083. By Andrea Azzarone

Add missing comma.

1084. By Andrea Azzarone

Get the attributes out of the translatable string.

1085. By Andrea Azzarone

Make GtkScrolledWindow of textview_livepatch hidden by default.

1086. By Andrea Azzarone

Make "Livepatch" string non transatable.

1087. By Andrea Azzarone

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

1088. By Andrea Azzarone

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

1089. By Andrea Azzarone

Use logging.debug instead of logging.warning.

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

Hi,

thanks for the review.

> Ok, some extra inline comment, also

All of them should be fixed now.

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

That was accidental. It should be fixed now.

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

I changed it from a warning to a debug string.

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

Done.

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

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

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

review: Needs Fixing
1090. By Andrea Azzarone

Don't make Livepatch tab name translatable.

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

Fixed.

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

Thanks

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'data/gtkbuilder/dialog-livepatch-error.ui'
--- data/gtkbuilder/dialog-livepatch-error.ui 2017-10-11 18:23:42 +0000
+++ data/gtkbuilder/dialog-livepatch-error.ui 2019-02-18 16:16:34 +0000
@@ -1,58 +1,139 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<!-- Generated with glade 3.18.3 -->2<!-- Generated with glade 3.22.0 -->
3<interface>3<interface>
4 <requires lib="gtk+" version="3.12"/>4 <requires lib="gtk+" version="3.12"/>
5 <object class="GtkMessageDialog" id="messagedialog_livepatch">5 <object class="GtkTextBuffer" id="textbuffer_message"/>
6 <property name="can_focus">False</property>6 <object class="GtkDialog" id="messagedialog_livepatch">
7 <property name="title">Livepatch</property>
8 <property name="resizable">False</property>
9 <property name="modal">True</property>
7 <property name="type_hint">dialog</property>10 <property name="type_hint">dialog</property>
8 <property name="message_type">error</property>11 <property name="urgency_hint">True</property>
9 <property name="text" translatable="yes">Sorry, there’s been a problem in setting up Canonical Livepatch.</property>12 <property name="deletable">False</property>
13 <property name="skip_taskbar_hint">True</property>
14 <property name="skip_pager_hint">True</property>
10 <child internal-child="vbox">15 <child internal-child="vbox">
11 <object class="GtkBox" id="messagedialog-vbox1">16 <object class="GtkBox">
12 <property name="can_focus">False</property>
13 <property name="orientation">vertical</property>17 <property name="orientation">vertical</property>
14 <property name="spacing">2</property>18 <property name="spacing">2</property>
15 <child internal-child="action_area">19 <child internal-child="action_area">
16 <object class="GtkButtonBox" id="messagedialog-action_area1">20 <object class="GtkButtonBox">
17 <property name="can_focus">False</property>
18 <property name="layout_style">end</property>21 <property name="layout_style">end</property>
19 <child>22 <child>
20 <object class="GtkButton" id="button_settings">23 <object class="GtkButton" id="button_settings">
21 <property name="label" translatable="yes">Settings…</property>24 <property name="label" translatable="yes">Settings…</property>
22 <property name="visible">True</property>
23 <property name="can_focus">True</property>25 <property name="can_focus">True</property>
24 <property name="receives_default">True</property>26 <property name="receives_default">False</property>
25 <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/>27 <signal name="clicked" handler="on_button_settings_clicked" swapped="no"/>
26 </object>28 </object>
27 <packing>29 <packing>
28 <property name="expand">True</property>30 <property name="expand">True</property>
29 <property name="fill">True</property>31 <property name="fill">True</property>
30 <property name="position">0</property>
31 </packing>32 </packing>
32 </child>33 </child>
33 <child>34 <child>
34 <object class="GtkButton" id="button_ignore">35 <object class="GtkButton" id="button_ignore">
35 <property name="label" translatable="yes">Ignore</property>36 <property name="label" translatable="yes">Ignore</property>
36 <property name="visible">True</property>37 <property name="visible">True</property>
38 <property name="has_focus">True</property>
37 <property name="can_focus">True</property>39 <property name="can_focus">True</property>
38 <property name="receives_default">True</property>40 <property name="receives_default">True</property>
39 <property name="yalign">0.51999998092651367</property>
40 <signal name="clicked" handler="on_button_ignore_clicked" swapped="no"/>41 <signal name="clicked" handler="on_button_ignore_clicked" swapped="no"/>
41 </object>42 </object>
42 <packing>43 <packing>
43 <property name="expand">True</property>44 <property name="expand">True</property>
44 <property name="fill">True</property>45 <property name="fill">True</property>
45 <property name="position">1</property>
46 </packing>46 </packing>
47 </child>47 </child>
48 </object>48 </object>
49 <packing>49 <packing>
50 <property name="expand">False</property>50 <property name="expand">False</property>
51 <property name="fill">False</property>51 <property name="fill">False</property>
52 <property name="position">0</property>52 </packing>
53 </child>
54 <child>
55 <object class="GtkBox">
56 <property name="visible">True</property>
57 <property name="border_width">12</property>
58 <property name="orientation">vertical</property>
59 <child>
60 <object class="GtkGrid">
61 <property name="visible">True</property>
62 <property name="row_spacing">12</property>
63 <property name="column_spacing">12</property>
64 <child>
65 <object class="GtkLabel" id="label_primary">
66 <property name="visible">True</property>
67 <property name="xalign">0</property>
68 </object>
69 <packing>
70 <property name="left_attach">1</property>
71 <property name="top_attach">0</property>
72 </packing>
73 </child>
74 <child>
75 <object class="GtkImage">
76 <property name="visible">True</property>
77 <property name="halign">center</property>
78 <property name="valign">start</property>
79 <property name="stock">gtk-dialog-error</property>
80 <property name="use_fallback">True</property>
81 <property name="icon_size">6</property>
82 </object>
83 <packing>
84 <property name="left_attach">0</property>
85 <property name="top_attach">0</property>
86 <property name="height">3</property>
87 </packing>
88 </child>
89 <child>
90 <object class="GtkLabel">
91 <property name="visible">True</property>
92 <property name="label" translatable="yes">The error was:</property>
93 <property name="xalign">0</property>
94 </object>
95 <packing>
96 <property name="left_attach">1</property>
97 <property name="top_attach">1</property>
98 </packing>
99 </child>
100 <child>
101 <object class="GtkTextView" id="treeview_message">
102 <property name="height_request">100</property>
103 <property name="visible">True</property>
104 <property name="hexpand">True</property>
105 <property name="vexpand">True</property>
106 <property name="pixels_above_lines">6</property>
107 <property name="pixels_below_lines">6</property>
108 <property name="editable">False</property>
109 <property name="wrap_mode">word</property>
110 <property name="left_margin">6</property>
111 <property name="right_margin">6</property>
112 <property name="cursor_visible">False</property>
113 <property name="buffer">textbuffer_message</property>
114 <property name="accepts_tab">False</property>
115 </object>
116 <packing>
117 <property name="left_attach">1</property>
118 <property name="top_attach">2</property>
119 </packing>
120 </child>
121 </object>
122 <packing>
123 <property name="expand">True</property>
124 <property name="fill">True</property>
125 </packing>
126 </child>
127 </object>
128 <packing>
129 <property name="expand">True</property>
130 <property name="fill">True</property>
53 </packing>131 </packing>
54 </child>132 </child>
55 </object>133 </object>
56 </child>134 </child>
135 <child type="titlebar">
136 <placeholder/>
137 </child>
57 </object>138 </object>
58</interface>139</interface>
59140
=== modified file 'data/gtkbuilder/main.ui'
--- data/gtkbuilder/main.ui 2017-08-24 14:40:18 +0000
+++ data/gtkbuilder/main.ui 2019-02-18 16:16:34 +0000
@@ -1,7 +1,13 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<!-- Generated with glade 3.18.3 -->2<!-- Generated with glade 3.22.0 -->
3<interface>3<interface>
4 <requires lib="gtk+" version="3.0"/>4 <requires lib="gtk+" version="3.22"/>
5 <object class="GtkListStore" id="model_livepatch_fixes">
6 <columns>
7 <!-- column-name fix -->
8 <column type="gchararray"/>
9 </columns>
10 </object>
5 <object class="GtkListStore" id="model_normal_updates_display">11 <object class="GtkListStore" id="model_normal_updates_display">
6 <columns>12 <columns>
7 <!-- column-name text -->13 <!-- column-name text -->
@@ -91,6 +97,7 @@
91 <object class="GtkTextBuffer" id="textbuffer1">97 <object class="GtkTextBuffer" id="textbuffer1">
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>
93 </object>99 </object>
100 <object class="GtkTextBuffer" id="textbuffer_livepatch"/>
94 <object class="GtkWindow" id="window_main">101 <object class="GtkWindow" id="window_main">
95 <property name="can_focus">False</property>102 <property name="can_focus">False</property>
96 <property name="border_width">6</property>103 <property name="border_width">6</property>
@@ -696,84 +703,6 @@
696 </packing>703 </packing>
697 </child>704 </child>
698 <child>705 <child>
699 <object class="GtkAlignment" id="alignment3">
700 <property name="visible">True</property>
701 <property name="can_focus">False</property>
702 <property name="left_padding">12</property>
703 <child>
704 <object class="GtkGrid" id="grid_livepatch">
705 <property name="visible">True</property>
706 <property name="can_focus">False</property>
707 <property name="halign">center</property>
708 <property name="hexpand">False</property>
709 <property name="vexpand">False</property>
710 <property name="row_spacing">6</property>
711 <property name="column_spacing">6</property>
712 <child>
713 <object class="GtkBox" id="hbox_livepatch">
714 <property name="visible">True</property>
715 <property name="can_focus">False</property>
716 <property name="halign">center</property>
717 <property name="spacing">6</property>
718 <child>
719 <object class="GtkLabel" id="label_livepatch_login">
720 <property name="visible">True</property>
721 <property name="can_focus">False</property>
722 </object>
723 <packing>
724 <property name="expand">False</property>
725 <property name="fill">True</property>
726 <property name="position">0</property>
727 </packing>
728 </child>
729 <child>
730 <object class="GtkButton" id="button_ubuntuone">
731 <property name="visible">True</property>
732 <property name="can_focus">True</property>
733 <property name="receives_default">True</property>
734 <property name="xalign">0</property>
735 </object>
736 <packing>
737 <property name="expand">False</property>
738 <property name="fill">True</property>
739 <property name="position">2</property>
740 </packing>
741 </child>
742 </object>
743 <packing>
744 <property name="left_attach">1</property>
745 <property name="top_attach">1</property>
746 </packing>
747 </child>
748 <child>
749 <object class="GtkCheckButton" id="checkbutton_livepatch">
750 <property name="label" translatable="yes">Use Canonical Livepatch to increase security between restarts</property>
751 <property name="use_action_appearance">False</property>
752 <property name="visible">True</property>
753 <property name="can_focus">True</property>
754 <property name="receives_default">False</property>
755 <property name="use_underline">True</property>
756 <property name="xalign">0</property>
757 <property name="draw_indicator">True</property>
758 </object>
759 <packing>
760 <property name="left_attach">1</property>
761 <property name="top_attach">0</property>
762 </packing>
763 </child>
764 <child>
765 <placeholder/>
766 </child>
767 </object>
768 </child>
769 </object>
770 <packing>
771 <property name="expand">True</property>
772 <property name="fill">True</property>
773 <property name="position">2</property>
774 </packing>
775 </child>
776 <child>
777 <object class="GtkAlignment" id="alignment15">706 <object class="GtkAlignment" id="alignment15">
778 <property name="visible">True</property>707 <property name="visible">True</property>
779 <property name="can_focus">False</property>708 <property name="can_focus">False</property>
@@ -820,7 +749,7 @@
820 <packing>749 <packing>
821 <property name="expand">False</property>750 <property name="expand">False</property>
822 <property name="fill">False</property>751 <property name="fill">False</property>
823 <property name="position">3</property>752 <property name="position">2</property>
824 </packing>753 </packing>
825 </child>754 </child>
826 </object>755 </object>
@@ -1187,6 +1116,214 @@
1187 <property name="tab_fill">False</property>1116 <property name="tab_fill">False</property>
1188 </packing>1117 </packing>
1189 </child>1118 </child>
1119 <child>
1120 <object class="GtkBox" id="vbox_livepatch">
1121 <property name="visible">True</property>
1122 <property name="can_focus">False</property>
1123 <property name="border_width">12</property>
1124 <property name="orientation">vertical</property>
1125 <property name="spacing">12</property>
1126 <child>
1127 <object class="GtkLabel" id="label_livepatch_description">
1128 <property name="visible">True</property>
1129 <property name="can_focus">False</property>
1130 <property name="label" translatable="yes">Canonical Livepatch helps keep your system secure by applying security updates that don't require a restart. &lt;a href="https://www.ubuntu.com/livepatch"&gt;Learn More&lt;/a&gt;</property>
1131 <property name="use_markup">True</property>
1132 <property name="wrap">True</property>
1133 <property name="max_width_chars">1</property>
1134 <property name="xalign">0</property>
1135 </object>
1136 <packing>
1137 <property name="expand">False</property>
1138 <property name="fill">True</property>
1139 <property name="position">0</property>
1140 </packing>
1141 </child>
1142 <child>
1143 <object class="GtkBox" id="hbox_switch">
1144 <property name="visible">True</property>
1145 <property name="can_focus">False</property>
1146 <property name="spacing">6</property>
1147 <child>
1148 <object class="GtkSwitch" id="switch_livepatch">
1149 <property name="visible">True</property>
1150 <property name="sensitive">False</property>
1151 <property name="can_focus">True</property>
1152 </object>
1153 <packing>
1154 <property name="expand">False</property>
1155 <property name="fill">True</property>
1156 <property name="position">0</property>
1157 </packing>
1158 </child>
1159 <child>
1160 <object class="GtkSpinner" id="spinner_livepatch">
1161 <property name="can_focus">False</property>
1162 </object>
1163 <packing>
1164 <property name="expand">False</property>
1165 <property name="fill">True</property>
1166 <property name="position">1</property>
1167 </packing>
1168 </child>
1169 <child>
1170 <object class="GtkLabel" id="label_livepatch_switch">
1171 <property name="visible">True</property>
1172 <property name="can_focus">False</property>
1173 </object>
1174 <packing>
1175 <property name="expand">False</property>
1176 <property name="fill">True</property>
1177 <property name="position">2</property>
1178 </packing>
1179 </child>
1180 <child>
1181 <object class="GtkButton" id="button_livepatch_login">
1182 <property name="can_focus">True</property>
1183 <property name="receives_default">True</property>
1184 </object>
1185 <packing>
1186 <property name="expand">False</property>
1187 <property name="fill">True</property>
1188 <property name="pack_type">end</property>
1189 <property name="position">3</property>
1190 </packing>
1191 </child>
1192 </object>
1193 <packing>
1194 <property name="expand">False</property>
1195 <property name="fill">True</property>
1196 <property name="position">1</property>
1197 </packing>
1198 </child>
1199 <child>
1200 <object class="GtkStack" id="stack_livepatch">
1201 <property name="can_focus">False</property>
1202 <property name="transition_type">crossfade</property>
1203 <property name="interpolate_size">True</property>
1204 <child>
1205 <object class="GtkScrolledWindow">
1206 <property name="visible">False</property>
1207 <property name="can_focus">True</property>
1208 <property name="shadow_type">in</property>
1209 <child>
1210 <object class="GtkTextView" id="textview_livepatch">
1211 <property name="visible">True</property>
1212 <property name="can_focus">True</property>
1213 <property name="pixels_above_lines">6</property>
1214 <property name="editable">False</property>
1215 <property name="wrap_mode">word</property>
1216 <property name="left_margin">6</property>
1217 <property name="right_margin">6</property>
1218 <property name="cursor_visible">False</property>
1219 <property name="buffer">textbuffer_livepatch</property>
1220 <property name="accepts_tab">False</property>
1221 </object>
1222 </child>
1223 </object>
1224 <packing>
1225 <property name="name">page_livepatch_message</property>
1226 </packing>
1227 </child>
1228 <child>
1229 <object class="GtkBox">
1230 <property name="visible">True</property>
1231 <property name="can_focus">False</property>
1232 <property name="orientation">vertical</property>
1233 <property name="spacing">12</property>
1234 <child>
1235 <object class="GtkLabel" id="label_livepatch_last_update">
1236 <property name="visible">True</property>
1237 <property name="can_focus">False</property>
1238 <property name="xalign">0</property>
1239 </object>
1240 <packing>
1241 <property name="expand">False</property>
1242 <property name="fill">True</property>
1243 <property name="position">0</property>
1244 </packing>
1245 </child>
1246 <child>
1247 <object class="GtkLabel" id="label_livepatch_header">
1248 <property name="visible">True</property>
1249 <property name="can_focus">False</property>
1250 <property name="xalign">0</property>
1251 </object>
1252 <packing>
1253 <property name="expand">False</property>
1254 <property name="fill">True</property>
1255 <property name="position">1</property>
1256 </packing>
1257 </child>
1258 <child>
1259 <object class="GtkScrolledWindow" id="scrolledwindow_livepatch_fixes">
1260 <property name="visible">True</property>
1261 <property name="can_focus">True</property>
1262 <property name="shadow_type">in</property>
1263 <child>
1264 <object class="GtkTreeView" id="treeview_livepatch">
1265 <property name="visible">True</property>
1266 <property name="can_focus">True</property>
1267 <property name="model">model_livepatch_fixes</property>
1268 <property name="headers_visible">False</property>
1269 <property name="enable_search">False</property>
1270 <property name="show_expanders">False</property>
1271 <child internal-child="selection">
1272 <object class="GtkTreeSelection"/>
1273 </child>
1274 <child>
1275 <object class="GtkTreeViewColumn">
1276 <property name="title" translatable="yes">column</property>
1277 <child>
1278 <object class="GtkCellRendererText">
1279 <property name="width_chars">100</property>
1280 <property name="wrap_mode">word</property>
1281 <property name="wrap_width">100</property>
1282 </object>
1283 <attributes>
1284 <attribute name="markup">0</attribute>
1285 </attributes>
1286 </child>
1287 </object>
1288 </child>
1289 </object>
1290 </child>
1291 </object>
1292 <packing>
1293 <property name="expand">True</property>
1294 <property name="fill">True</property>
1295 <property name="position">2</property>
1296 </packing>
1297 </child>
1298 </object>
1299 <packing>
1300 <property name="name">page_livepatch_status</property>
1301 <property name="position">1</property>
1302 </packing>
1303 </child>
1304 </object>
1305 <packing>
1306 <property name="expand">True</property>
1307 <property name="fill">True</property>
1308 <property name="position">2</property>
1309 </packing>
1310 </child>
1311 </object>
1312 <packing>
1313 <property name="position">6</property>
1314 </packing>
1315 </child>
1316 <child type="tab">
1317 <object class="GtkLabel" id="label_livepatch">
1318 <property name="visible">True</property>
1319 <property name="can_focus">False</property>
1320 <property name="label">Livepatch</property>
1321 </object>
1322 <packing>
1323 <property name="position">6</property>
1324 <property name="tab_fill">False</property>
1325 </packing>
1326 </child>
1190 </object>1327 </object>
1191 <packing>1328 <packing>
1192 <property name="expand">True</property>1329 <property name="expand">True</property>
@@ -1246,6 +1383,9 @@
1246 </child>1383 </child>
1247 </object>1384 </object>
1248 </child>1385 </child>
1386 <child type="titlebar">
1387 <placeholder/>
1388 </child>
1249 </object>1389 </object>
1250 <object class="GtkSizeGroup" id="sizegroup1">1390 <object class="GtkSizeGroup" id="sizegroup1">
1251 <widgets>1391 <widgets>
12521392
=== added file 'data/icons/16x16/apps/livepatch.svg'
1253Binary 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 differ1393Binary 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
=== added file 'data/icons/24x24/apps/livepatch.svg'
1254Binary 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 differ1394Binary 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
=== added file 'data/icons/48x48/apps/livepatch.svg'
1255Binary 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 differ1395Binary 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
=== added file 'data/icons/64x64/apps/livepatch.svg'
1256Binary 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 differ1396Binary 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
=== added file 'data/icons/scalable/apps/livepatch.svg'
--- data/icons/scalable/apps/livepatch.svg 1970-01-01 00:00:00 +0000
+++ data/icons/scalable/apps/livepatch.svg 2019-02-18 16:16:34 +0000
@@ -0,0 +1,1 @@
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>
0\ No newline at end of file2\ No newline at end of file
13
=== added file 'data/software-properties-livepatch.desktop.in'
--- data/software-properties-livepatch.desktop.in 1970-01-01 00:00:00 +0000
+++ data/software-properties-livepatch.desktop.in 2019-02-18 16:16:34 +0000
@@ -0,0 +1,12 @@
1[Desktop Entry]
2Keywords=Livepatch;
3Exec=/usr/bin/software-properties-gtk --open-tab=6
4Icon=livepatch
5Terminal=false
6Type=Application
7OnlyShowIn=GNOME;
8Categories=GTK;Settings;HardwareSettings
9X-AppStream-Ignore=true
10Name=Livepatch
11_Comment=Manage Canonical Livepatch
12_Keywords=Security;Update;
013
=== modified file 'debian/changelog'
--- debian/changelog 2019-02-13 00:00:29 +0000
+++ debian/changelog 2019-02-18 16:16:34 +0000
@@ -1,3 +1,4 @@
1<<<<<<< TREE
1software-properties (0.97.2) disco; urgency=medium2software-properties (0.97.2) disco; urgency=medium
23
3 * Install python3-aptdaemon with software-properties-qt.4 * Install python3-aptdaemon with software-properties-qt.
@@ -10,6 +11,19 @@
1011
11 -- Hans P. Möller <hmollercl@lubuntu.me> Sat, 09 Feb 2019 17:08:39 -060012 -- Hans P. Möller <hmollercl@lubuntu.me> Sat, 09 Feb 2019 17:08:39 -0600
1213
14=======
15software-properties (0.96.33) UNRELEASED; urgency=medium
16
17 * Move Livepatch UI in a different tab.
18 * Add a Livepatch desktop file.
19 * debian/control:
20 - Remove gir1.2-secret-1 dep, it is no longer needed.
21 - Add python3-dateutil and python3-requests-unixsocket dep, they are
22 required by LivepatchService.py.
23
24 -- Andrea Azzarone <andrea.azzarone@canonical.com> Tue, 29 Jan 2019 18:29:18 +0000
25
26>>>>>>> MERGE-SOURCE
13software-properties (0.96.32) disco; urgency=medium27software-properties (0.96.32) disco; urgency=medium
1428
15 * softwareproperties/gtk/DialogAuth.py:29 * softwareproperties/gtk/DialogAuth.py:
1630
=== modified file 'debian/control'
--- debian/control 2019-02-13 00:00:29 +0000
+++ debian/control 2019-02-18 16:16:34 +0000
@@ -57,10 +57,11 @@
57 python3-gi,57 python3-gi,
58 gir1.2-gtk-3.0,58 gir1.2-gtk-3.0,
59 gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1),59 gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1),
60 gir1.2-secret-1,
61 gir1.2-snapd-1,60 gir1.2-snapd-1,
62 python3-aptdaemon.gtk3widgets,61 python3-aptdaemon.gtk3widgets,
62 python3-dateutil,
63 python3-distro-info,63 python3-distro-info,
64 python3-requests-unixsocket,
64 software-properties-common,65 software-properties-common,
65 ubuntu-drivers-common (>= 1:0.2.75),66 ubuntu-drivers-common (>= 1:0.2.75),
66 python3-gi,67 python3-gi,
6768
=== modified file 'debian/software-properties-gtk.install'
--- debian/software-properties-gtk.install 2018-04-17 11:36:55 +0000
+++ debian/software-properties-gtk.install 2019-02-18 16:16:34 +0000
@@ -5,6 +5,7 @@
5debian/tmp/usr/share/icons5debian/tmp/usr/share/icons
6debian/tmp/usr/share/applications/software-properties-gtk.desktop6debian/tmp/usr/share/applications/software-properties-gtk.desktop
7debian/tmp/usr/share/applications/software-properties-drivers.desktop7debian/tmp/usr/share/applications/software-properties-drivers.desktop
8debian/tmp/usr/share/applications/software-properties-livepatch.desktop
8debian/tmp/usr/share/metainfo/software-properties-gtk.appdata.xml9debian/tmp/usr/share/metainfo/software-properties-gtk.appdata.xml
9debian/tmp/usr/share/glib-2.0/schemas10debian/tmp/usr/share/glib-2.0/schemas
10#debian/tmp/usr/share/gnome/help/software-properties11#debian/tmp/usr/share/gnome/help/software-properties
1112
=== modified file 'po/POTFILES.in'
--- po/POTFILES.in 2018-07-14 10:11:32 +0000
+++ po/POTFILES.in 2019-02-18 16:16:34 +0000
@@ -5,6 +5,7 @@
5data/software-properties-gtk.appdata.xml.in5data/software-properties-gtk.appdata.xml.in
6data/software-properties-qt.desktop.in6data/software-properties-qt.desktop.in
7data/software-properties-drivers.desktop.in7data/software-properties-drivers.desktop.in
8data/software-properties-livepatch.desktop.in
8software-properties-gtk9software-properties-gtk
9software-properties-qt10software-properties-qt
10add-apt-repository11add-apt-repository
@@ -28,8 +29,12 @@
28softwareproperties/gtk/DialogEdit.py29softwareproperties/gtk/DialogEdit.py
29softwareproperties/gtk/DialogAdd.py30softwareproperties/gtk/DialogAdd.py
30softwareproperties/gtk/DialogCacheOutdated.py31softwareproperties/gtk/DialogCacheOutdated.py
32softwareproperties/gtk/DialogLivepatchError.py
33softwareproperties/gtk/LivepatchPage.py
31softwareproperties/CountryInformation.py34softwareproperties/CountryInformation.py
32softwareproperties/AptAuth.py35softwareproperties/AptAuth.py
36softwareproperties/LivepatchService.py
37softwareproperties/LivepatchSnap.py
33[type: gettext/glade]data/designer/dialog_mirror.ui38[type: gettext/glade]data/designer/dialog_mirror.ui
34[type: gettext/glade]data/designer/dialog_edit.ui39[type: gettext/glade]data/designer/dialog_edit.ui
35[type: gettext/glade]data/designer/main.ui40[type: gettext/glade]data/designer/main.ui
@@ -41,3 +46,4 @@
41[type: gettext/glade]data/gtkbuilder/dialog-mirror.ui46[type: gettext/glade]data/gtkbuilder/dialog-mirror.ui
42[type: gettext/glade]data/gtkbuilder/dialog-add.ui47[type: gettext/glade]data/gtkbuilder/dialog-add.ui
43[type: gettext/glade]data/gtkbuilder/dialog-auth.ui48[type: gettext/glade]data/gtkbuilder/dialog-auth.ui
49[type: gettext/glade]data/gtkbuilder/dialog-livepatch-error.ui
4450
=== modified file 'setup.cfg'
--- setup.cfg 2018-07-14 10:11:32 +0000
+++ setup.cfg 2019-02-18 16:16:34 +0000
@@ -4,6 +4,7 @@
4desktop_files=[("share/applications",4desktop_files=[("share/applications",
5 ("data/software-properties-gtk.desktop.in",5 ("data/software-properties-gtk.desktop.in",
6 "data/software-properties-drivers.desktop.in",6 "data/software-properties-drivers.desktop.in",
7 "data/software-properties-livepatch.desktop.in",
7 "data/software-properties-qt.desktop.in",),8 "data/software-properties-qt.desktop.in",),
8 )9 )
9 ]10 ]
1011
=== added file 'softwareproperties/LivepatchService.py'
--- softwareproperties/LivepatchService.py 1970-01-01 00:00:00 +0000
+++ softwareproperties/LivepatchService.py 2019-02-18 16:16:34 +0000
@@ -0,0 +1,254 @@
1#
2# Copyright (c) 2019 Canonical
3#
4# Authors:
5# Andrea Azzarone <andrea.azzarone@canonical.com>
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20# USA
21
22from gettext import gettext as _
23import logging
24
25import gi
26from gi.repository import Gio, GLib, GObject
27
28try:
29 import dateutil.parser
30 import requests_unixsocket
31
32 gi.require_version('Snapd', '1')
33 from gi.repository import Snapd
34except(ImportError, ValueError):
35 pass
36
37from softwareproperties.gtk.utils import (
38 has_gnome_online_accounts,
39 is_current_distro_lts,
40 is_current_distro_supported,
41 retry
42)
43
44from softwareproperties.LivepatchSnap import LivepatchSnap
45
46
47def datetime_parser(json_dict):
48 for (key, value) in json_dict.items():
49 try:
50 json_dict[key] = dateutil.parser.parse(value)
51 except (ValueError, TypeError):
52 pass
53 return json_dict
54
55class LivepatchAvailability:
56 FALSE = 0
57 TRUE = 1
58 NO_CONNECTIVITY=3
59 CHECKING = 2
60
61
62class LivepatchService(GObject.GObject):
63
64 # Constants
65 STATUS_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd.sock/status'
66 ENABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/enable'
67 DISABLE_ENDPOINT = 'http+unix://%2Fvar%2Fsnap%2Fcanonical-livepatch%2Fcurrent%2Flivepatchd-priv.sock/disable'
68 LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
69
70 ENABLE_ERROR_MSG = _('Failed to enable Livepatch: {}')
71 DISABLE_ERROR_MSG = _('Failed to disable Livepatch: {}')
72
73 # GObject.GObject
74 __gproperties__ = {
75 'availability': (
76 int, None, None,
77 LivepatchAvailability.FALSE,
78 LivepatchAvailability.CHECKING,
79 LivepatchAvailability.FALSE,
80 GObject.PARAM_READABLE),
81 'availability-message': (
82 str, None, None, None, GObject.PARAM_READABLE),
83 'enabled': (
84 bool, None, None, False, GObject.PARAM_READABLE),
85 }
86
87 def __init__(self):
88 GObject.GObject.__init__(self)
89
90 self._timeout_id = 0
91
92 self._snap = LivepatchSnap()
93 self._session = requests_unixsocket.Session()
94
95 # Init Properties
96 self._availability = LivepatchAvailability.FALSE
97 self._availability_message = None
98 lp_file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
99 self._enabled = lp_file.query_exists()
100
101 # Monitor connectivity status
102 self._nm = Gio.NetworkMonitor.get_default()
103 self._nm.connect('notify::connectivity', self._network_changed_cb)
104
105 # Monitor status of canonical-livepatch
106 self._lp_monitor = lp_file.monitor_file(Gio.FileMonitorFlags.NONE)
107 self._lp_monitor.connect('changed', self._livepatch_enabled_changed_cb)
108
109 def do_get_property(self, pspec):
110 if pspec.name == 'availability':
111 return self._availability
112 elif pspec.name == 'availability-message':
113 return self._availability_message
114 elif pspec.name == 'enabled':
115 return self._enabled
116 else:
117 raise AssertionError
118
119 # Public API
120 def trigger_availability_check(self):
121 """Trigger a Livepatch availability check to be executed after a short
122 timeout. Multiple triggers will result in a single request.
123
124 A notify::availability will be emitted when the check starts, and
125 another one when the check ends.
126 """
127 def _update_availability():
128 # each rule is a tuple of two elements, a callable and a string. The
129 # string rapresents the error message that needs to be shown if the
130 # callable returns false.
131 rules = [
132 (lambda: self._snap.get_status() != Snapd.SnapStatus.UNKNOWN,
133 _('Canonical Livepatch snap is not available.')),
134 (has_gnome_online_accounts,
135 _('Gnome Online Accounts is required to enable Livepatch.')),
136 (is_current_distro_lts,
137 _('Livepatch is not available for this release.')),
138 (is_current_distro_supported,
139 _('The current release is no longer supported.'))]
140
141 if self._nm.props.connectivity != Gio.NetworkConnectivity.FULL:
142 self._availability = LivepatchAvailability.NO_CONNECTIVITY
143 self._availability_message = None
144 else:
145 for func, message in rules:
146 if not func():
147 self._availability = LivepatchAvailability.FALSE
148 self._availability_message = message
149 break
150 else:
151 self._availability = LivepatchAvailability.TRUE
152 self._availability_message = None
153
154 self.notify('availability')
155 self.notify('availability-message')
156
157 self._timeout_id = 0
158 return False
159
160 self._availability = LivepatchAvailability.CHECKING
161 self._availability_message = None
162 self.notify('availability')
163 self.notify('availability-message')
164
165 if self._timeout_id == 0:
166 self._timeout_id = GLib.timeout_add_seconds(3, _update_availability)
167
168 def set_enabled(self, enabled, token):
169 """Enable or disable Canonical Livepatch in the current system. This
170 function will return once the operation succeeded or failed.
171
172 Args:
173 enabled(bool): wheater to enable or disable the service.
174 token(str): the authentication token to be used to enable Canonical
175 Livepatch service.
176
177 Returns:
178 (False, '') if successful, (True, error_message) otherwise.
179 """
180 if self._enabled == enabled:
181 return False, ''
182
183 if not enabled:
184 return self._disable_service()
185 elif self._snap.get_status() == Snapd.SnapStatus.ACTIVE:
186 return self._enable_service(token)
187 else:
188 success, msg = self._snap.enable_or_install()
189 return self._enable_service(token) if success else (True, msg)
190
191 def get_status(self):
192 """Synchronously retrieve the status of Canonical Livepatch.
193
194 Returns:
195 str: The status. A valid string for success, None otherwise.
196 """
197 try:
198 params = {'verbosity': 3, 'format': 'json'}
199 r = self._session.get(self.STATUS_ENDPOINT, params=params)
200 return r.json(object_hook=datetime_parser)
201 except Exception as e:
202 logging.debug('Failed to get Livepatch status: {}'.format(str(e)))
203 return None
204
205 # Private methods
206 def _enable_service(self, token):
207 """Enable Canonical Livepatch in the current system. This function will
208 return once the operation succeeded or failed.
209
210 Args:
211 token(str): the authentication token to be used to enable Canonical
212 Livepatch service.
213
214 Returns:
215 (False, '') if successful, (True, error_message) otherwise.
216 """
217 try:
218 return self._enable_service_with_retry(token)
219 except Exception as e:
220 return True, self.ENABLE_ERROR_MSG.format(str(e))
221
222 @retry(Exception)
223 def _enable_service_with_retry(self, token):
224 params = {'auth-token': token}
225 r = self._session.put(self.ENABLE_ENDPOINT, params=params)
226 return not r.ok, '' if r.ok else self.ENABLE_ERROR_MSG.format(r.text)
227
228 def _disable_service(self):
229 """Disable Canonical Livepatch in the current system. This function will
230 return once the operation succeeded or failed.
231
232 Returns:
233 (False, '') if successful, (True, error_message) otherwise.
234 """
235 try:
236 return self._disable_service_with_retry()
237 except Exception as e:
238 return True, self.DISABLE_ERROR_MSG.format(str(e))
239
240
241 @retry(Exception)
242 def _disable_service_with_retry(self):
243 r = self._session.put(self.DISABLE_ENDPOINT)
244 return not r.ok, '' if r.ok else self.DISABLE_ERROR_MSG.format(r.text)
245
246 # Signals handlers
247 def _network_changed_cb(self, monitor, network_available):
248 self.trigger_availability_check()
249
250 def _livepatch_enabled_changed_cb(self, fm, file, other_file, event_type):
251 enabled = file.query_exists()
252 if self._enabled != enabled:
253 self._enabled = enabled
254 self.notify('enabled')
0255
=== added file 'softwareproperties/LivepatchSnap.py'
--- softwareproperties/LivepatchSnap.py 1970-01-01 00:00:00 +0000
+++ softwareproperties/LivepatchSnap.py 2019-02-18 16:16:34 +0000
@@ -0,0 +1,135 @@
1#
2# Copyright (c) 2019 Canonical
3#
4# Authors:
5# Andrea Azzarone <andrea.azzarone@canonical.com>
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20# USA
21
22from gettext import gettext as _
23import logging
24
25import gi
26from gi.repository import Gio, GLib
27
28try:
29 gi.require_version('Snapd', '1')
30 from gi.repository import Snapd
31except(ImportError, ValueError):
32 pass
33
34
35class LivepatchSnap(object):
36
37 # Constants
38 SNAP_NAME = 'canonical-livepatch'
39
40 # Public API
41 def __init__(self):
42 self._snapd_client = Snapd.Client()
43 self._cancellable = Gio.Cancellable()
44
45 def get_status(self):
46 """ Get the status of canonical-livepatch snap.
47
48 Returns:
49 Snapd.SnapStatus.Enun: An enum indicating the status of the snap.
50 """
51 snap = self._get_raw_snap()
52 return snap.get_status() if snap else Snapd.SnapStatus.UNKNOWN
53
54 def enable_or_install(self):
55 """Enable or install canonical-livepatch snap.
56
57 Returns:
58 (True, '') if successful, (False, error_message) otherwise.
59 """
60 status = self.get_status()
61
62 if status == Snapd.SnapStatus.ACTIVE:
63 logging.warning('{} snap is already active'.format(self.SNAP_NAME))
64 return True, ''
65 elif status == Snapd.SnapStatus.AVAILABLE:
66 return self._install()
67 elif status == Snapd.SnapStatus.INSTALLED:
68 return self._enable()
69 else:
70 logging.warning('{} snap is in an unknown state'.format(self.SNAP_NAME))
71 return False, _('Canonical Livepatch snap cannot be installed.')
72
73 # Private methods
74 def _get_raw_snap(self):
75 """Get the Sanpd.Snap raw object of the canonical-livepatch snapd.
76
77 Returns:
78 Sanpd.Snap if successful, None otherwise.
79 """
80 try:
81 snap = self._snapd_client.get_snap_sync(
82 name=self.SNAP_NAME,
83 cancellable=self._cancellable)
84 except GLib.Error as e:
85 logging.debug('Snapd.Client.get_snap_sync failed: {}'.format(e.message))
86 snap = None
87
88 if snap:
89 return snap
90
91 try:
92 (snaps, ignored) = self._snapd_client.find_sync(
93 flags=Snapd.FindFlags.MATCH_NAME,
94 query=self.SNAP_NAME,
95 cancellable=self._cancellable)
96 snap = snaps[0]
97 except GLib.Error as e:
98 logging.debug('Snapd.Client.find_sync failed: {}'.format(e.message))
99
100 return snap
101
102 def _install(self):
103 """Install canonical-livepatch snap.
104
105 Returns:
106 (True, '') if successful, (False, error_message) otherwise.
107 """
108 assert self.get_status() == Snapd.SnapStatus.AVAILABLE
109
110 try:
111 self._snapd_client.install2_sync(
112 flags=Snapd.InstallFlags.NONE,
113 name=self.SNAP_NAME,
114 cancellable=self._cancellable)
115 except GLib.Error as e:
116 return False, _('Canonical Livepatch snap cannot be installed: {}'.format(e.message))
117 else:
118 return True, ''
119
120 def _enable(self):
121 """Enable the canonical-livepatch snap.
122
123 Returns:
124 (True, '') if successful, (False, error_message) otherwise.
125 """
126 assert self.get_status() == Snapd.SnapStatus.INSTALLED
127
128 try:
129 self._snapd_client.enable_sync(
130 name=self.SNAP_NAME,
131 cancellable=self._cancellable)
132 except GLib.Error as e:
133 return False, _('Canonical Livepatch snap cannot be enabled: {}'.format(e.message))
134 else:
135 return True, ''
0136
=== modified file 'softwareproperties/SoftwareProperties.py'
--- softwareproperties/SoftwareProperties.py 2018-12-18 13:46:40 +0000
+++ softwareproperties/SoftwareProperties.py 2019-02-18 16:16:34 +0000
@@ -32,7 +32,6 @@
32import os32import os
33import glob33import glob
34import shutil34import shutil
35import subprocess
36import threading35import threading
37import atexit36import atexit
38import tempfile37import tempfile
@@ -65,15 +64,8 @@
65from . import ppa64from . import ppa
66from . import cloudarchive65from . import cloudarchive
6766
68import gi
69from gi.repository import Gio67from gi.repository import Gio
7068
71try:
72 gi.require_version('Snapd', '1')
73 from gi.repository import Snapd
74except (ImportError, ValueError):
75 pass
76
77_SHORTCUT_FACTORIES = [69_SHORTCUT_FACTORIES = [
78 ppa.shortcut_handler,70 ppa.shortcut_handler,
79 cloudarchive.shortcut_handler,71 cloudarchive.shortcut_handler,
@@ -100,9 +92,6 @@
100 RELEASE_UPGRADES_NEVER : 'never',92 RELEASE_UPGRADES_NEVER : 'never',
101 }93 }
10294
103 # file to monitor canonical-livepatch status
104 LIVEPATCH_RUNNING_FILE = '/var/snap/canonical-livepatch/common/machine-token'
105
106 def __init__(self, datadir=None, options=None, rootdir="/"):95 def __init__(self, datadir=None, options=None, rootdir="/"):
107 """ Provides the core functionality to configure the used software 96 """ Provides the core functionality to configure the used software
108 repositories, the corresponding authentication keys and 97 repositories, the corresponding authentication keys and
@@ -871,147 +860,6 @@
871 except:860 except:
872 return False861 return False
873862
874 #
875 # Livepatch
876 #
877 def init_snapd(self):
878 self.snapd_client = Snapd.Client()
879
880 def get_livepatch_snap_async(self, callback):
881 assert self.snapd_client
882 self.snapd_client.list_one_async('canonical-livepatch',
883 self.cancellable,
884 self.on_list_one_ready_cb,
885 callback)
886
887 def on_list_one_ready_cb(self, source_object, result, user_data):
888 callback = user_data
889 try:
890 snap = source_object.list_one_finish(result)
891 except:
892 snap = None
893 if snap:
894 if callback:
895 callback(snap)
896 return
897 else:
898 assert self.snapd_client
899 self.snapd_client.find_async(Snapd.FindFlags.MATCH_NAME,
900 'canonical-livepatch',
901 self.cancellable,
902 self.on_find_ready_cb,
903 callback)
904
905 def on_find_ready_cb(self, source_object, result, user_data):
906 callback = user_data
907 try:
908 snaps = source_object.find_finish(result)[0]
909 except:
910 snaps = list()
911 snap = snaps[0] if len(snaps) else None
912 if callback:
913 callback(snap)
914
915 def get_livepatch_snap_status(self, snap):
916 if snap is None:
917 return Snapd.SnapStatus.UNKNOWN
918 return snap.get_status()
919
920 # glib-snapd does not keep track of the status of the snap. Use this decorator
921 # to make it easy to write async functions that will always have an updated
922 # snap object.
923 def require_livepatch_snap(func):
924 def get_livepatch_snap_and_call(*args, **kwargs):
925 return args[0].get_livepatch_snap_async(lambda snap: func(snap=snap, *args, **kwargs))
926 return get_livepatch_snap_and_call
927
928 def is_livepatch_enabled(self):
929 file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
930 return file.query_exists(None)
931
932 @require_livepatch_snap
933 def set_livepatch_enabled_async(self, enabled, token, callback, snap=None):
934 status = self.get_livepatch_snap_status(snap)
935 if status == Snapd.SnapStatus.UNKNOWN:
936 if callback:
937 callback(True, _("Canonical Livepatch snap cannot be installed."))
938 elif status == Snapd.SnapStatus.ACTIVE:
939 if enabled:
940 error = self.enable_livepatch_service(token)
941 else:
942 error = self.disable_livepatch_service()
943 if callback:
944 callback(len(error) > 0, error)
945 elif status == Snapd.SnapStatus.INSTALLED:
946 if enabled:
947 self.snapd_client.enable_async(name='canonical-livepatch',
948 cancellable=self.cancellable,
949 callback=self.livepatch_enable_snap_cb,
950 user_data=(callback, token))
951 else:
952 if callback:
953 callback(False, "")
954 elif status == Snapd.SnapStatus.AVAILABLE:
955 if enabled:
956 self.snapd_client.install_async(name='canonical-livepatch',
957 cancellable=self.cancellable,
958 callback=self.livepatch_install_snap_cb,
959 user_data=(callback, token))
960 else:
961 if callback:
962 callback(False, "")
963
964 def livepatch_enable_snap_cb(self, source_object, result, user_data):
965 (callback, token) = user_data
966 try:
967 if source_object.enable_finish(result):
968 error = self.enable_livepatch_service(token)
969 if callback:
970 callback(len(error) > 0, error)
971 except Exception:
972 if callback:
973 callback(True, _("Canonical Livepatch snap cannot be enabled."))
974
975 def livepatch_install_snap_cb(self, source_object, result, user_data):
976 (callback, token) = user_data
977 try:
978 if source_object.install_finish(result):
979 error = self.enable_livepatch_service(token)
980 if callback:
981 callback(len(error) > 0, error)
982 except Exception:
983 if callback:
984 callback(True, _("Canonical Livepatch snap cannot be installed."))
985
986 def enable_livepatch_service(self, token):
987 generic_error = _("Canonical Livepatch cannot be enabled.")
988
989 if self.is_livepatch_enabled():
990 return ""
991
992 try:
993 subprocess.check_output(['/snap/bin/canonical-livepatch', 'enable', token], stderr=subprocess.STDOUT)
994 return ""
995 except subprocess.CalledProcessError as e:
996 return e.output if e.output else generic_error
997 except:
998 return generic_error
999
1000
1001 def disable_livepatch_service(self):
1002 generic_error = _("Canonical Livepatch cannot be disabled.")
1003
1004 if not self.is_livepatch_enabled():
1005 return ""
1006
1007 try:
1008 subprocess.check_output(['/snap/bin/canonical-livepatch', 'disable'], stderr=subprocess.STDOUT)
1009 return ""
1010 except subprocess.CalledProcessError as e:
1011 return e.output if e.output else generic_error
1012 except:
1013 return generic_error
1014
1015def shortcut_handler(shortcut):863def shortcut_handler(shortcut):
1016 for factory in _SHORTCUT_FACTORIES:864 for factory in _SHORTCUT_FACTORIES:
1017 ret = factory(shortcut)865 ret = factory(shortcut)
1018866
=== modified file 'softwareproperties/dbus/SoftwarePropertiesDBus.py'
--- softwareproperties/dbus/SoftwarePropertiesDBus.py 2019-01-12 13:06:12 +0000
+++ softwareproperties/dbus/SoftwarePropertiesDBus.py 2019-02-18 16:16:34 +0000
@@ -25,10 +25,12 @@
25import subprocess25import subprocess
26import tempfile26import tempfile
27import sys27import sys
28import threading
2829
29from aptsources.sourceslist import SourceEntry30from aptsources.sourceslist import SourceEntry
3031
31from dbus.mainloop.glib import DBusGMainLoop32from dbus.mainloop.glib import DBusGMainLoop
33from softwareproperties.LivepatchService import LivepatchService
32from softwareproperties.SoftwareProperties import SoftwareProperties34from softwareproperties.SoftwareProperties import SoftwareProperties
3335
34DBUS_BUS_NAME = 'com.ubuntu.SoftwareProperties'36DBUS_BUS_NAME = 'com.ubuntu.SoftwareProperties'
@@ -61,7 +63,7 @@
61 self.enforce_polkit = True63 self.enforce_polkit = True
62 logging.debug("waiting for connections")64 logging.debug("waiting for connections")
6365
64 self.init_snapd()66 self._livepatch_service = LivepatchService()
6567
66 # override set_modified_sourceslist to emit a signal68 # override set_modified_sourceslist to emit a signal
67 def save_sourceslist(self):69 def save_sourceslist(self):
@@ -336,9 +338,13 @@
336 sender_keyword="sender", connection_keyword="conn",338 sender_keyword="sender", connection_keyword="conn",
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'))
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):
341 def enable_thread_func():
342 ret = self._livepatch_service.set_enabled(enabled, token)
343 GLib.idle_add(lambda: reply_handler(*ret))
344
339 self._check_policykit_privilege(345 self._check_policykit_privilege(
340 sender, conn, "com.ubuntu.softwareproperties.applychanges")346 sender, conn, "com.ubuntu.softwareproperties.applychanges")
341 self.set_livepatch_enabled_async(enabled, token, reply_handler)347 threading.Thread(target=enable_thread_func).start()
342348
343 # helper from jockey349 # helper from jockey
344 def _check_policykit_privilege(self, sender, conn, privilege):350 def _check_policykit_privilege(self, sender, conn, privilege):
345351
=== modified file 'softwareproperties/gtk/DialogLivepatchError.py'
--- softwareproperties/gtk/DialogLivepatchError.py 2018-03-21 14:49:38 +0000
+++ softwareproperties/gtk/DialogLivepatchError.py 2019-02-18 16:16:34 +0000
@@ -1,5 +1,5 @@
1#1#
2# Copyright (c) 2017-2018 Canonical2# Copyright (c) 2017-2019 Canonical
3#3#
4# Authors:4# Authors:
5# Andrea Azzarone <andrea.azzarone@canonical.com>5# Andrea Azzarone <andrea.azzarone@canonical.com>
@@ -21,6 +21,8 @@
2121
22import os22import os
2323
24from gettext import gettext as _
25
24from softwareproperties.gtk.utils import (26from softwareproperties.gtk.utils import (
25 setup_ui,27 setup_ui,
26)28)
@@ -31,20 +33,27 @@
31 RESPONSE_SETTINGS = 10033 RESPONSE_SETTINGS = 100
32 RESPONSE_IGNORE = 10134 RESPONSE_IGNORE = 101
3335
36 primary = _("Sorry, there's been a problem with setting up Canonical Livepatch.")
37
34 def __init__(self, parent, datadir):38 def __init__(self, parent, datadir):
35 """setup up the gtk dialog"""39 """setup up the gtk dialog"""
36 self.parent = parent40 self.parent = parent
3741
38 setup_ui(self, os.path.join(datadir, "gtkbuilder",42 setup_ui(
39 "dialog-livepatch-error.ui"), domain="software-properties")43 self,
44 os.path.join(datadir, "gtkbuilder", "dialog-livepatch-error.ui"),
45 domain="software-properties")
4046
41 self.dialog = self.messagedialog_livepatch47 self.dialog = self.messagedialog_livepatch
42 self.dialog.use_header_bar = True
43 self.dialog.set_transient_for(parent)48 self.dialog.set_transient_for(parent)
4449
45 def run(self, error, show_settings_button):50 def run(self, error, show_settings_button):
46 self.dialog.format_secondary_markup(51 p = "<span weight=\"bold\" size=\"larger\">{}</span>".format(self.primary)
47 "The error was: \"%s\"" % error.strip())52 self.label_primary.set_markup(p)
53
54 textbuffer = self.treeview_message.get_buffer()
55 textbuffer.set_text(error)
56
48 self.button_settings.set_visible(show_settings_button)57 self.button_settings.set_visible(show_settings_button)
49 res = self.dialog.run()58 res = self.dialog.run()
50 self.dialog.hide()59 self.dialog.hide()
5160
=== added file 'softwareproperties/gtk/LivepatchPage.py'
--- softwareproperties/gtk/LivepatchPage.py 1970-01-01 00:00:00 +0000
+++ softwareproperties/gtk/LivepatchPage.py 2019-02-18 16:16:34 +0000
@@ -0,0 +1,375 @@
1#
2# Copyright (c) 2019 Canonical
3#
4# Authors:
5# Andrea Azzarone <andrea.azzarone@canonical.com>
6#
7# This program is free software; you can redistribute it and/or
8# modify it under the terms of the GNU General Public License as
9# published by the Free Software Foundation; either version 2 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20# USA
21
22import datetime
23import gettext
24from gettext import gettext as _
25import gi
26gi.require_version("Gtk", "3.0")
27from gi.repository import GLib, GObject, Gtk
28import logging
29
30from softwareproperties.GoaAuth import GoaAuth
31from softwareproperties.LivepatchService import (
32 LivepatchService,
33 LivepatchAvailability)
34from .DialogAuth import DialogAuth
35from .DialogLivepatchError import DialogLivepatchError
36
37
38class LivepatchPage(object):
39
40 # Constants
41 COMMON_ISSUE_URL = 'https://wiki.ubuntu.com/Kernel/Livepatch#CommonIssues'
42 GENERIC_ERR_MSG = _('Canonical Livepatch has experienced an internal error.'
43 ' Please refer to {} for further information.'.format(COMMON_ISSUE_URL))
44
45 def __init__(self, parent):
46 self._parent = parent
47
48 self._timeout_handler = -1
49 self._waiting_livepatch_response = False
50
51 self._lps = LivepatchService()
52 self._auth = GoaAuth()
53
54 # Connect signals
55 self._lps.connect(
56 'notify::availability', self._lps_availability_changed_cb)
57 self._lps.connect(
58 'notify::enabled', self._lps_enabled_changed_cb)
59 self._auth.connect(
60 'notify', self._auth_changed_cb)
61 self._state_set_handler = self._parent.switch_livepatch.connect(
62 'state-set', self._switch_state_set_cb)
63 self._parent.button_livepatch_login.connect(
64 'clicked', self._button_livepatch_login_clicked_cb)
65
66 self._lps.trigger_availability_check()
67
68 @property
69 def waiting_livepatch_response(self):
70 return self._waiting_livepatch_response
71
72 # Private methods
73 def _trigger_ui_update(self, skip=False, error_message=None):
74 """Trigger the update of every single user interface component according
75 to the current state.
76
77 Args:
78 skip (bool): whether to trigger the update after a small timeout.
79 Defaults to False.
80 error_message (str): error message to display. Defaults to None.
81 """
82 def do_ui_update():
83 self._timeout_handler = -1
84
85 self._update_switch()
86 self._update_spinner()
87 self._update_switch_label()
88 self._update_auth_button()
89 self._update_stack(error_message)
90
91 return False
92
93 if self._timeout_handler > 0:
94 GObject.source_remove(self._timeout_handler)
95 self._timeout_handler = -1
96
97 if skip:
98 do_ui_update()
99 else:
100 self._timeout_handler = GLib.timeout_add_seconds(2, do_ui_update)
101
102 def _update_switch(self):
103 """Update the state of the on/off switch."""
104 switch = self._parent.switch_livepatch
105
106 availability = self._lps.props.availability
107 enabled = self._lps.props.enabled
108 logged = self._auth.logged
109
110 switch.set_sensitive(
111 availability == LivepatchAvailability.TRUE and
112 (enabled or logged))
113
114 if self._waiting_livepatch_response:
115 return
116
117 self._parent.switch_livepatch.handler_block(self._state_set_handler)
118 switch.set_state(switch.get_sensitive() and enabled)
119 self._parent.switch_livepatch.handler_unblock(self._state_set_handler)
120
121 def _update_spinner(self):
122 """Update the state of the in-progress spinner."""
123 spinner = self._parent.spinner_livepatch
124 availability = self._lps.props.availability
125
126 spinner.set_visible(availability == LivepatchAvailability.CHECKING)
127 spinner.props.active = (availability == LivepatchAvailability.CHECKING)
128
129 def _update_switch_label(self):
130 """Update the text of the label next to the on/off switch."""
131 availability = self._lps.props.availability
132 logged = self._auth.logged
133
134 if availability == LivepatchAvailability.CHECKING:
135 msg = _('Checking availability…')
136 elif availability == LivepatchAvailability.NO_CONNECTIVITY:
137 msg = _('Livepatch requires an Internet connection.')
138 elif availability == LivepatchAvailability.FALSE:
139 msg = _('Livepatch is not available for this system.')
140 else:
141 if self._parent.switch_livepatch.get_active():
142 msg = _("Livepatch is on.")
143 elif not logged:
144 msg = _("To use Livepatch you need to sign in.")
145 else:
146 msg = _("Livepatch is off.")
147
148 self._parent.label_livepatch_switch.set_label(msg)
149
150 def _update_auth_button(self):
151 """Update the state and the label of the authentication button."""
152 button = self._parent.button_livepatch_login
153
154 availability = self._lps.props.availability
155 logged = self._auth.logged
156
157 button.set_visible(
158 availability == LivepatchAvailability.TRUE and
159 not self._parent.switch_livepatch.get_active())
160 button.set_label(_('Sign Out') if logged else _('Sign In…'))
161
162 def _update_stack(self, error_message):
163 """Update the state of the stack.
164
165 If livepatch is not available nothing will be shown, if an error
166 occurred an error message will be shown in a text view, otherwise the
167 current livepatch status (e.g. a list of CVE fixes) will be shown.
168
169 Args:
170 error_message (str): error message to display.
171 """
172 availability = self._lps.props.availability
173 availability_message = self._lps.props.availability_message
174
175 has_error = (
176 error_message or
177 (availability == LivepatchAvailability.FALSE and
178 availability_message is not None))
179
180 if has_error:
181 self._parent.stack_livepatch.set_visible(True)
182 self._parent.stack_livepatch.set_visible_child_name('page_livepatch_message')
183 text_buffer = self._parent.textview_livepatch.get_buffer()
184 text_buffer.delete(
185 text_buffer.get_start_iter(), text_buffer.get_end_iter())
186 text_buffer.insert_markup(
187 text_buffer.get_end_iter(),
188 error_message or availability_message, -1)
189 return
190
191 if availability == LivepatchAvailability.CHECKING or not self._parent.switch_livepatch.get_active():
192 self._parent.stack_livepatch.set_visible(False)
193 else:
194 self._update_status()
195
196 def _format_timedelta(self, td):
197 days = td.days
198 hours = td.seconds // 3600
199 minutes = td.seconds // 60
200
201 if days > 0:
202 return gettext.ngettext(
203 '({} day ago)',
204 '({} days ago)',
205 days).format(days)
206 elif hours > 0:
207 return gettext.ngettext(
208 '({} hour ago)',
209 '({} hours ago)',
210 hours).format(hours)
211 elif minutes > 0:
212 return gettext.ngettext(
213 '({} minute ago)',
214 '({} minutes ago)',
215 minutes).format(minutes)
216 else:
217 return ''
218
219 def _datetime_to_str(self, dt):
220 gdt = GLib.DateTime.new_from_unix_utc(dt.timestamp())
221 td = datetime.datetime.now(dt.tzinfo) - dt
222 return '{} {}'.format(
223 gdt.to_local().format('%x %H:%M'),
224 self._format_timedelta(td))
225
226 def _update_status(self):
227 """Populate the UI to reflect the Livepatch status"""
228 status = self._lps.get_status()
229
230 if status is None:
231 if not self._waiting_livepatch_response:
232 self._trigger_ui_update(skip=True, error_message=_('Failed to retrieve Livepatch status.'))
233 return
234
235 self._parent.stack_livepatch.set_visible(True)
236 self._parent.stack_livepatch.set_visible_child_name('page_livepatch_status')
237
238 check_state = status['Status'][0]['Livepatch']['CheckState'] if status else None
239 state = status['Status'][0]['Livepatch']['State'] if status else None
240
241 if check_state == 'check-failed':
242 self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG)
243 return
244
245 if state in ['applied-with-bug', 'apply-failed', 'unknown']:
246 self._trigger_ui_update(skip=True, error_message=self.GENERIC_ERR_MSG)
247 return
248
249 self._parent.label_livepatch_last_update.set_label(
250 _("Last check for updates: {}").format(
251 self._datetime_to_str(status['Last-Check']) if status else _('None yet')))
252
253 if state in ['unapplied', 'nothing-to-apply'] or state == None:
254 self._parent.label_livepatch_header.set_label(_('No updates currently applied.'))
255 self._parent.scrolledwindow_livepatch_fixes.set_visible(False)
256 elif state == 'applied':
257 self._parent.label_livepatch_header.set_label(_('Updates currently applied:'))
258 self._parent.scrolledwindow_livepatch_fixes.set_visible(True)
259 self._update_fixes(status['Status'][0]['Livepatch']['Fixes'])
260 else:
261 logging.warning('Livepatch status contains an invalid state: {}'.format(state))
262
263 if check_state == 'needs-check' or state == 'unapplied':
264 self._trigger_ui_update()
265
266 def _update_fixes(self, fixes):
267 """Populate the UI to show the list of applied CVE fixes."""
268 treeview = self._parent.treeview_livepatch
269 liststore = treeview.get_model()
270 liststore.clear()
271
272 for fix in fixes:
273 fix_iter = liststore.append()
274 liststore.set(fix_iter, [0], [self._format_fix(fix)])
275
276 def _format_fix(self, fix):
277 """Format a fix in a UI friendly text."""
278 return '<b>{}</b>\n{}'.format(
279 fix['Name'], fix['Description'].replace('\n', ' '))
280
281 def _do_login(self):
282 """Start the authentication flow to retrieve the livepatch token."""
283 dialog = DialogAuth(self._parent.window_main, self._parent.datadir)
284
285 if dialog.run() == Gtk.ResponseType.OK:
286 self._auth.login(dialog.account)
287 self._parent.switch_livepatch.set_state(True)
288
289 def _do_logout(self):
290 """Start the de-authentication flow."""
291 self._auth.logout()
292
293 # Signals handler
294 def _lps_availability_changed_cb(self, o, v):
295 self._trigger_ui_update(skip=True)
296
297 def _lps_enabled_changed_cb(self, o, v):
298 if self._waiting_livepatch_response:
299 return
300 self._trigger_ui_update(skip=False)
301
302 def _auth_changed_cb(self, o, v):
303 self._trigger_ui_update(skip=True)
304
305 def _switch_state_set_cb(self, widget, state):
306 if not self._waiting_livepatch_response:
307 self._waiting_livepatch_response = True
308
309 token = self._auth.token or ''
310 self._parent.backend.SetLivepatchEnabled(
311 state, token,
312 reply_handler=self._enabled_reply_handler,
313 error_handler=self._enabled_error_handler,
314 timeout=1200)
315
316 self._trigger_ui_update(skip=True)
317 self._parent.switch_livepatch.set_state(state)
318
319 return False
320
321 def _button_livepatch_login_clicked_cb(self, button):
322 if self._auth.logged:
323 self._do_logout()
324 else:
325 self._do_login()
326
327 def _show_error_dialog(self, message):
328 dialog = DialogLivepatchError(
329 self._parent.window_main,
330 self._parent.datadir)
331
332 response = dialog.run(
333 error=message,
334 show_settings_button=not self._parent.window_main.is_visible())
335
336 if response == DialogLivepatchError.RESPONSE_SETTINGS:
337 self._parent.window_main.show()
338 self._parent.notebook_main.set_current_page(6)
339 elif not self._parent.window_main.is_visible():
340 self._parent.on_close_button(None)
341
342 # DBus replay handlers
343 def _enabled_reply_handler(self, is_error, prompt):
344 if self._parent.switch_livepatch.get_active() == self._lps.props.enabled:
345 self._waiting_livepatch_response = False
346 self._trigger_ui_update(skip=True)
347
348 if not self._parent.window_main.is_visible():
349 self._parent.on_close_button(None)
350 else:
351 if is_error:
352 self._waiting_livepatch_response = False
353 self._trigger_ui_update(skip=True)
354 self._show_error_dialog(prompt)
355 else:
356 # The user tooggled on/off the switch while we were waiting
357 # livepatch to respond back.
358 self._parent.backend.SetLivepatchEnabled(
359 self._parent.switch_livepatch.get_active(),
360 self._auth.token,
361 reply_handler=self._enabled_reply_handler,
362 error_handler=self._enabled_error_handler,
363 timeout=1200)
364
365 def _enabled_error_handler(self, e):
366 self._waiting_livepatch_response = False
367 self._trigger_ui_update(skip=True)
368
369 if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
370 logging.warning("Authentication canceled, changes have not been saved")
371
372 if not self._parent.window_main.is_visible():
373 self._parent.on_close_button(None)
374 else:
375 self._show_error_dialog(str(e))
0376
=== modified file 'softwareproperties/gtk/SimpleGtkbuilderApp.py'
--- softwareproperties/gtk/SimpleGtkbuilderApp.py 2016-09-09 07:09:03 +0000
+++ softwareproperties/gtk/SimpleGtkbuilderApp.py 2019-02-18 16:16:34 +0000
@@ -38,6 +38,9 @@
38 def on_activate(self, data=None):38 def on_activate(self, data=None):
39 self.add_window(self.window_main)39 self.add_window(self.window_main)
4040
41 if not self.window_main.is_visible():
42 self.window_main.show()
43
41 def run(self):44 def run(self):
42 """45 """
43 Starts the main loop of processing events checking for Control-C.46 Starts the main loop of processing events checking for Control-C.
4447
=== modified file 'softwareproperties/gtk/SoftwarePropertiesGtk.py'
--- softwareproperties/gtk/SoftwarePropertiesGtk.py 2018-12-18 13:46:40 +0000
+++ softwareproperties/gtk/SoftwarePropertiesGtk.py 2019-02-18 16:16:34 +0000
@@ -27,9 +27,6 @@
2727
28import apt28import apt
29import apt_pkg29import apt_pkg
30import aptsources.distro
31from datetime import datetime
32import distro_info
33import dbus30import dbus
34from gettext import gettext as _31from gettext import gettext as _
35import gettext32import gettext
@@ -52,11 +49,9 @@
52from .DialogEdit import DialogEdit49from .DialogEdit import DialogEdit
53from .DialogCacheOutdated import DialogCacheOutdated50from .DialogCacheOutdated import DialogCacheOutdated
54from .DialogAddSourcesList import DialogAddSourcesList51from .DialogAddSourcesList import DialogAddSourcesList
55from .DialogLivepatchError import DialogLivepatchError52from .LivepatchPage import LivepatchPage
56from .DialogAuth import DialogAuth
5753
58import softwareproperties54import softwareproperties
59from softwareproperties.GoaAuth import GoaAuth
60import softwareproperties.distro55import softwareproperties.distro
61from softwareproperties.SoftwareProperties import SoftwareProperties56from softwareproperties.SoftwareProperties import SoftwareProperties
62import softwareproperties.SoftwareProperties57import softwareproperties.SoftwareProperties
@@ -85,8 +80,6 @@
85 STORE_VISIBLE80 STORE_VISIBLE
86) = list(range(5))81) = list(range(5))
8782
88LIVEPATCH_TIMEOUT = 1200
89
9083
91def error(parent_window, summary, msg):84def error(parent_window, summary, msg):
92 """ show a error dialog """85 """ show a error dialog """
@@ -1045,7 +1038,9 @@
1045 d = DialogCacheOutdated(self.window_main,1038 d = DialogCacheOutdated(self.window_main,
1046 self.datadir)1039 self.datadir)
1047 d.run()1040 d.run()
1048 if self.waiting_livepatch_response:1041
1042 self.quit_when_livepatch_responds = False
1043 if self.livepatch_page.waiting_livepatch_response:
1049 self.quit_when_livepatch_responds = True1044 self.quit_when_livepatch_responds = True
1050 self.hide()1045 self.hide()
1051 else:1046 else:
@@ -1483,164 +1478,6 @@
1483 else:1478 else:
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."))
14851480
1486 #
1487 # Livepatch
1488 #
1489 def init_livepatch(self):1481 def init_livepatch(self):
1490 self.goa_auth = GoaAuth()1482 self.livepatch_page = LivepatchPage(self)
1491 self.waiting_livepatch_response = False1483
1492 self.quit_when_livepatch_responds = False
1493
1494 if not self.is_livepatch_supported():
1495 self.grid_livepatch.set_visible(False)
1496 return
1497
1498 self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1499 self.on_goa_auth_changed()
1500
1501 # hacky way to monitor if livepatch is enabled or not
1502 file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
1503 self.lp_monitor = file.monitor_file(Gio.FileMonitorFlags.NONE)
1504
1505 # connect to signals
1506 self.handlers[self.goa_auth] = \
1507 self.goa_auth.connect('notify', lambda o, p: self.on_goa_auth_changed())
1508 self.handlers[self.checkbutton_livepatch] = \
1509 self.checkbutton_livepatch.connect('toggled', self.on_checkbutton_livepatch_toggled)
1510 self.handlers[self.button_ubuntuone] = \
1511 self.button_ubuntuone.connect('clicked', self.on_button_ubuntuone_clicked)
1512 self.handlers[self.lp_monitor] = \
1513 self.lp_monitor.connect('changed', self.on_livepatch_status_changed)
1514
1515 def has_online_accounts(self):
1516 try:
1517 d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop')
1518 return d != None
1519 except Exception:
1520 return False
1521
1522 def is_livepatch_supported(self):
1523 distro = aptsources.distro.get_distro()
1524 di = distro_info.UbuntuDistroInfo()
1525
1526 return self.has_online_accounts() and \
1527 di.is_lts(distro.codename) and \
1528 distro.codename in di.supported(datetime.now().date())
1529
1530 def on_goa_auth_changed(self):
1531 if self.goa_auth.logged:
1532 self.button_ubuntuone.set_label(_('Sign Out'))
1533
1534 if self.goa_auth.token:
1535 self.checkbutton_livepatch.set_sensitive(True)
1536 self.label_livepatch_login.set_label(_('Signed in as %s' % self.goa_auth.username))
1537 else:
1538 self.checkbutton_livepatch.set_sensitive(False)
1539 text = _('%s isn\'t authorized to use Livepatch.' % self.goa_auth.username)
1540 text = "<span color='red'>" + text + "</span>"
1541 self.label_livepatch_login.set_markup(text)
1542 else:
1543 if self.is_livepatch_enabled() and not self.waiting_livepatch_response:
1544 # Allow the user to disable livepatch even if
1545 # the account expired (see LP: #1768797)
1546 self.checkbutton_livepatch.set_sensitive(True)
1547 self.label_livepatch_login.set_label(_('Livepatch is active.'))
1548 else:
1549 self.checkbutton_livepatch.set_sensitive(False)
1550 self.label_livepatch_login.set_label(_('To use Livepatch you need to sign in.'))
1551
1552 self.button_ubuntuone.set_label(_('Sign In…'))
1553
1554 def on_livepatch_status_changed(self, file_monitor, file, other_file, event_type):
1555 if not self.waiting_livepatch_response:
1556 self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1557 self.on_goa_auth_changed()
1558
1559 def on_button_ubuntuone_clicked(self, button):
1560 if self.goa_auth.logged:
1561 self.do_logout()
1562 else:
1563 self.do_login()
1564
1565 def do_login(self):
1566 try:
1567 # Show login dialog!
1568 dialog = DialogAuth(self.window_main, self.datadir)
1569 response = dialog.run()
1570 except Exception as e:
1571 logging.error(e)
1572 error(self.window_main,
1573 _("Error enabling Canonical Livepatch"),
1574 _("Please check your Internet connection."))
1575 else:
1576 if response == Gtk.ResponseType.OK:
1577 self.goa_auth.login(dialog.account)
1578 if self.goa_auth.logged:
1579 self.checkbutton_livepatch.set_active(True)
1580
1581 def do_logout(self):
1582 self.checkbutton_livepatch.set_active(False)
1583 self.goa_auth.logout()
1584
1585 def on_checkbutton_livepatch_toggled(self, checkbutton):
1586 if self.waiting_livepatch_response:
1587 return
1588
1589 self.waiting_livepatch_response = True
1590
1591 token = ''
1592 enabled = False
1593 if self.checkbutton_livepatch.get_active():
1594 enabled = True
1595 token = self.goa_auth.token if self.goa_auth.token else ''
1596 self.backend.SetLivepatchEnabled(enabled, token,
1597 reply_handler=self.livepatch_enabled_reply_handler,
1598 error_handler=self.livepatch_enabled_error_handler,
1599 timeout=LIVEPATCH_TIMEOUT)
1600
1601 def livepatch_enabled_reply_handler(self, is_error, prompt):
1602 self.sync_checkbutton_livepatch(is_error, prompt)
1603
1604 def livepatch_enabled_error_handler(self, e):
1605 if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1606 logging.error("Authentication canceled, changes have not been saved")
1607 self.sync_checkbutton_livepatch(is_error=True, prompt=None)
1608 else:
1609 self.sync_checkbutton_livepatch(is_error=True, prompt=str(e))
1610
1611 def sync_checkbutton_livepatch(self, is_error, prompt):
1612 if is_error:
1613 self.waiting_livepatch_response = False
1614 self.checkbutton_livepatch.handler_block(self.handlers[self.checkbutton_livepatch])
1615 self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1616 self.checkbutton_livepatch.handler_unblock(self.handlers[self.checkbutton_livepatch])
1617
1618 if prompt:
1619 dialog = DialogLivepatchError(self.window_main, self.datadir)
1620 response = dialog.run(prompt, show_settings_button=self.quit_when_livepatch_responds)
1621 if response == DialogLivepatchError.RESPONSE_SETTINGS:
1622 self.window_main.show()
1623 self.quit_when_livepatch_responds = False
1624 else:
1625 do_dbus_call = False
1626 if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active():
1627 do_dbus_call = True
1628 enabled = False
1629 token = ''
1630 elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active():
1631 do_dbus_call = True
1632 enabled = True
1633 token = self.goa_auth.token if self.goa_auth.token else ''
1634 else:
1635 self.waiting_livepatch_response = False
1636
1637 if do_dbus_call:
1638 self.backend.SetLivepatchEnabled(enabled, token,
1639 reply_handler=self.livepatch_enabled_reply_handler,
1640 error_handler=self.livepatch_enabled_error_handler,
1641 timeout=LIVEPATCH_TIMEOUT)
1642
1643 self.on_goa_auth_changed()
1644
1645 if self.quit_when_livepatch_responds:
1646 self.on_close_button(self.button_close)
16471484
=== modified file 'softwareproperties/gtk/utils.py'
--- softwareproperties/gtk/utils.py 2016-09-09 07:09:03 +0000
+++ softwareproperties/gtk/utils.py 2019-02-18 16:16:34 +0000
@@ -18,13 +18,19 @@
1818
19from __future__ import print_function19from __future__ import print_function
2020
21import aptsources.distro
22from datetime import datetime
23import distro_info
24from functools import wraps
21import gi25import gi
22gi.require_version("Gtk", "3.0")26gi.require_version("Gtk", "3.0")
23from gi.repository import Gtk27from gi.repository import Gio, Gtk
2428
25import logging29import logging
26LOG=logging.getLogger(__name__)30LOG=logging.getLogger(__name__)
2731
32import time
33
28def setup_ui(self, path, domain):34def setup_ui(self, path, domain):
29 # setup ui35 # setup ui
30 self.builder = Gtk.Builder()36 self.builder = Gtk.Builder()
@@ -37,3 +43,52 @@
37 setattr(self, name, o)43 setattr(self, name, o)
38 else:44 else:
39 logging.debug("can not get name for object '%s'" % o)45 logging.debug("can not get name for object '%s'" % o)
46
47def has_gnome_online_accounts():
48 try:
49 d = Gio.DesktopAppInfo.new('gnome-online-accounts-panel.desktop')
50 return d != None
51 except Exception:
52 return False
53
54def is_current_distro_lts():
55 distro = aptsources.distro.get_distro()
56 di = distro_info.UbuntuDistroInfo()
57 return di.is_lts(distro.codename)
58
59def is_current_distro_supported():
60 distro = aptsources.distro.get_distro()
61 di = distro_info.UbuntuDistroInfo()
62 return distro.codename in di.supported(datetime.now().date())
63
64def retry(exceptions, tries=10, delay=0.1, backoff=2):
65 """
66 Retry calling the decorated function using an exponential backoff.
67
68 Args:
69 exceptions: The exception to check. may be a tuple of
70 exceptions to check.
71 tries: Number of times to try (not retry) before giving up.
72 delay: Initial delay between retries in seconds.
73 backoff: Backoff multiplier (e.g. value of 2 will double the delay
74 each retry).
75 """
76 def deco_retry(f):
77
78 @wraps(f)
79 def f_retry(*args, **kwargs):
80 mtries, mdelay = tries, delay
81 while mtries > 1:
82 try:
83 return f(*args, **kwargs)
84 except exceptions as e:
85 msg = '{}, Retrying in {} seconds...'.format(e, mdelay)
86 logging.warning(msg)
87 time.sleep(mdelay)
88 mtries -= 1
89 mdelay *= backoff
90 return f(*args, **kwargs)
91
92 return f_retry # true decorator
93
94 return deco_retry

Subscribers

People subscribed via source and target branches

to status/vote changes: