Merge ~azzar1/software-properties:ubuntu/bionic-livepatch into software-properties:ubuntu/bionic

Proposed by Andrea Azzarone
Status: Merged
Merged at revision: 03978fc2a7159c878eb2fb33c79ed7ca45f4b08c
Proposed branch: ~azzar1/software-properties:ubuntu/bionic-livepatch
Merge into: software-properties:ubuntu/bionic
Diff against target: 2640 lines (+1452/-626)
23 files modified
data/gtkbuilder/dialog-auth.ui (+93/-77)
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 (+12/-0)
debian/control (+7/-2)
debian/gbp.conf (+3/-0)
debian/software-properties-gtk.install (+1/-0)
po/POTFILES.in (+6/-0)
setup.cfg (+1/-0)
softwareproperties/GoaAuth.py (+16/-5)
softwareproperties/LivepatchService.py (+254/-0)
softwareproperties/LivepatchSnap.py (+135/-0)
softwareproperties/SoftwareProperties.py (+0/-152)
softwareproperties/dbus/SoftwarePropertiesDBus.py (+8/-2)
softwareproperties/gtk/DialogAuth.py (+132/-117)
softwareproperties/gtk/DialogLivepatchError.py (+15/-6)
softwareproperties/gtk/LivepatchPage.py (+375/-0)
softwareproperties/gtk/SimpleGtkbuilderApp.py (+3/-0)
softwareproperties/gtk/SoftwarePropertiesGtk.py (+5/-168)
softwareproperties/gtk/utils.py (+56/-1)
tests/aptroot/etc/apt/apt.conf.d/.keep (+0/-0)
Reviewer Review Type Date Requested Status
Sebastien Bacher Approve
Review via email: mp+365684@code.launchpad.net

Commit message

* Backport Livepatch changes from Disco (LP: #1823761):
  - Implement new design for authentication dialog.
  - Add livepatch desktop file and icon.
  - Move Livepatch UI in a diffrent tab.

To post a comment you must log in.
Revision history for this message
Sebastien Bacher (seb128) wrote :

Thanks, the code changes look fine it works as expected (tested with CVE applied, they were correctly listed)

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/data/gtkbuilder/dialog-auth.ui b/data/gtkbuilder/dialog-auth.ui
index b630fff..ab664ce 100644
--- a/data/gtkbuilder/dialog-auth.ui
+++ b/data/gtkbuilder/dialog-auth.ui
@@ -1,114 +1,130 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<!-- Generated with glade 3.20.4 -->2<!-- Generated with glade 3.22.1 -->
3<interface>3<interface>
4 <requires lib="gtk+" version="3.10"/>4 <requires lib="gtk+" version="3.10"/>
5 <object class="GtkListStore" id="liststore_account">
6 <columns>
7 <!-- column-name Id -->
8 <column type="gchararray"/>
9 <!-- column-name Email -->
10 <column type="gchararray"/>
11 <!-- column-name Account -->
12 <column type="GObject"/>
13 </columns>
14 </object>
5 <object class="GtkDialog" id="dialog_auth">15 <object class="GtkDialog" id="dialog_auth">
6 <property name="can_focus">False</property>
7 <property name="resizable">False</property>16 <property name="resizable">False</property>
8 <property name="modal">True</property>
9 <property name="destroy_with_parent">True</property>
10 <property name="type_hint">dialog</property>17 <property name="type_hint">dialog</property>
11 <property name="deletable">False</property>18 <child>
19 <placeholder/>
20 </child>
12 <child internal-child="vbox">21 <child internal-child="vbox">
13 <object class="GtkBox" id="box_dialog">22 <object class="GtkBox">
14 <property name="can_focus">False</property>23 <property name="border_width">6</property>
15 <property name="orientation">vertical</property>24 <property name="orientation">vertical</property>
16 <property name="spacing">2</property>25 <property name="spacing">2</property>
17 <child internal-child="action_area">26 <child internal-child="action_area">
18 <object class="GtkButtonBox" id="dialog-action_area1">27 <object class="GtkButtonBox">
19 <property name="can_focus">False</property>28 <child>
29 <object class="GtkButton" id="button_add_another">
30 <property name="label" translatable="yes">Add another…</property>
31 <property name="visible">True</property>
32 <property name="can_focus">True</property>
33 <property name="receives_default">True</property>
34 <signal name="clicked" handler="_button_add_another_clicked_cb" swapped="no"/>
35 </object>
36 <packing>
37 <property name="expand">True</property>
38 <property name="secondary">True</property>
39 <property name="non_homogeneous">True</property>
40 </packing>
41 </child>
42 <child>
43 <object class="GtkButton" id="button_cancel">
44 <property name="label" translatable="yes">Cancel</property>
45 <property name="visible">True</property>
46 <property name="can_focus">True</property>
47 <property name="receives_default">True</property>
48 <signal name="clicked" handler="_button_cancel_clicked_cb" swapped="no"/>
49 </object>
50 <packing>
51 <property name="expand">True</property>
52 <property name="non_homogeneous">True</property>
53 </packing>
54 </child>
55 <child>
56 <object class="GtkButton" id="button_continue">
57 <property name="visible">True</property>
58 <property name="can_focus">True</property>
59 <property name="receives_default">True</property>
60 <signal name="clicked" handler="_button_continue_clicked_cb" swapped="no"/>
61 </object>
62 <packing>
63 <property name="expand">True</property>
64 <property name="non_homogeneous">True</property>
65 </packing>
66 </child>
20 </object>67 </object>
21 <packing>68 <packing>
22 <property name="expand">False</property>69 <property name="expand">True</property>
23 <property name="fill">False</property>
24 <property name="position">0</property>
25 </packing>70 </packing>
26 </child>71 </child>
27 <child>72 <child>
28 <object class="GtkGrid" id="main_grid">73 <object class="GtkBox">
29 <property name="visible">True</property>74 <property name="visible">True</property>
30 <property name="can_focus">False</property>75 <property name="halign">start</property>
31 <property name="border_width">12</property>76 <property name="border_width">12</property>
32 <property name="row_spacing">12</property>77 <property name="spacing">18</property>
33 <property name="column_spacing">12</property>
34 <child>78 <child>
35 <object class="GtkLabel" id="label_title">79 <object class="GtkImage">
36 <property name="visible">True</property>80 <property name="visible">True</property>
37 <property name="can_focus">False</property>81 <property name="halign">start</property>
38 <property name="hexpand">True</property>82 <property name="valign">start</property>
39 <property name="label" translatable="yes">To enable Livepatch choose an Ubuntu Single Sign-on account.</property>83 <property name="icon_name">software-properties</property>
40 <property name="wrap">True</property>84 <property name="icon_size">6</property>
41 <property name="xalign">0</property>
42 </object>85 </object>
43 <packing>
44 <property name="left_attach">0</property>
45 <property name="top_attach">0</property>
46 </packing>
47 </child>86 </child>
48 <child>87 <child>
49 <object class="GtkFrame" id="main_frame">88 <object class="GtkBox" id="box_auth">
50 <property name="visible">True</property>89 <property name="visible">True</property>
51 <property name="can_focus">False</property>90 <property name="halign">start</property>
52 <property name="label_xalign">0</property>91 <property name="orientation">vertical</property>
92 <property name="spacing">12</property>
53 <child>93 <child>
54 <object class="GtkListBox" id="listbox_accounts">94 <object class="GtkLabel" id="label_header">
95 <property name="name">label_header</property>
55 <property name="visible">True</property>96 <property name="visible">True</property>
56 <property name="can_focus">False</property>97 <property name="halign">start</property>
57 <property name="selection_mode">none</property>98 <property name="valign">start</property>
99 <property name="use_markup">True</property>
100 <property name="justify">fill</property>
101 <property name="wrap">True</property>
102 <property name="max_width_chars">40</property>
103 </object>
104 </child>
105 <child>
106 <object class="GtkComboBox" id="combobox_account">
107 <property name="halign">start</property>
108 <property name="model">liststore_account</property>
58 <child>109 <child>
59 <object class="GtkListBoxRow" id="listboxrow_new_account">110 <object class="GtkCellRendererText"/>
60 <property name="visible">True</property>111 <attributes>
61 <property name="can_focus">False</property>112 <attribute name="text">1</attribute>
62 <child>113 </attributes>
63 <object class="GtkLabel" id="label_new_account">
64 <property name="height_request">48</property>
65 <property name="visible">True</property>
66 <property name="can_focus">False</property>
67 <property name="halign">center</property>
68 <property name="valign">center</property>
69 <property name="label" translatable="yes">&lt;b&gt;Use another account...&lt;/b&gt;</property>
70 <property name="use_markup">True</property>
71 <property name="justify">center</property>
72 </object>
73 </child>
74 </object>
75 </child>114 </child>
76 </object>115 </object>
77 </child>116 </child>
117 <child>
118 <object class="GtkLabel" id="label_account">
119 <property name="halign">start</property>
120 </object>
121 </child>
78 </object>122 </object>
79 <packing>
80 <property name="left_attach">0</property>
81 <property name="top_attach">1</property>
82 <property name="width">2</property>
83 </packing>
84 </child>123 </child>
85 </object>124 </object>
86 <packing>
87 <property name="expand">False</property>
88 <property name="fill">True</property>
89 <property name="position">1</property>
90 </packing>
91 </child>
92 </object>
93 </child>
94 <child type="titlebar">
95 <object class="GtkHeaderBar">
96 <property name="visible">True</property>
97 <property name="can_focus">False</property>
98 <property name="title" translatable="yes">Choose an account</property>
99 <child>
100 <object class="GtkButton" id="button_cancel">
101 <property name="label">gtk-cancel</property>
102 <property name="visible">True</property>
103 <property name="can_focus">True</property>
104 <property name="receives_default">True</property>
105 <property name="use_stock">True</property>
106 </object>
107 </child>125 </child>
108 </object>126 </object>
109 </child>127 </child>
110 <action-widgets>
111 <action-widget response="-6">button_cancel</action-widget>
112 </action-widgets>
113 </object>128 </object>
114</interface>129</interface>
130
diff --git a/data/gtkbuilder/dialog-livepatch-error.ui b/data/gtkbuilder/dialog-livepatch-error.ui
index 9d64306..6ee1f44 100644
--- a/data/gtkbuilder/dialog-livepatch-error.ui
+++ b/data/gtkbuilder/dialog-livepatch-error.ui
@@ -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>
diff --git a/data/gtkbuilder/main.ui b/data/gtkbuilder/main.ui
index 146a9ed..557e844 100644
--- a/data/gtkbuilder/main.ui
+++ b/data/gtkbuilder/main.ui
@@ -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">True</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>
diff --git a/data/icons/16x16/apps/livepatch.svg b/data/icons/16x16/apps/livepatch.svg
1252new file mode 1006441392new file mode 100644
index 0000000..6d82412
1253Binary files /dev/null and b/data/icons/16x16/apps/livepatch.svg differ1393Binary files /dev/null and b/data/icons/16x16/apps/livepatch.svg differ
diff --git a/data/icons/24x24/apps/livepatch.svg b/data/icons/24x24/apps/livepatch.svg
1254new file mode 1006441394new file mode 100644
index 0000000..8c13c57
1255Binary files /dev/null and b/data/icons/24x24/apps/livepatch.svg differ1395Binary files /dev/null and b/data/icons/24x24/apps/livepatch.svg differ
diff --git a/data/icons/48x48/apps/livepatch.svg b/data/icons/48x48/apps/livepatch.svg
1256new file mode 1006441396new file mode 100644
index 0000000..65a3049
1257Binary files /dev/null and b/data/icons/48x48/apps/livepatch.svg differ1397Binary files /dev/null and b/data/icons/48x48/apps/livepatch.svg differ
diff --git a/data/icons/64x64/apps/livepatch.svg b/data/icons/64x64/apps/livepatch.svg
1258new file mode 1006441398new file mode 100644
index 0000000..46c3dea
1259Binary files /dev/null and b/data/icons/64x64/apps/livepatch.svg differ1399Binary files /dev/null and b/data/icons/64x64/apps/livepatch.svg differ
diff --git a/data/icons/scalable/apps/livepatch.svg b/data/icons/scalable/apps/livepatch.svg
1260new file mode 1006441400new file mode 100644
index 0000000..8bdc8b5
--- /dev/null
+++ b/data/icons/scalable/apps/livepatch.svg
@@ -0,0 +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
diff --git a/data/software-properties-livepatch.desktop.in b/data/software-properties-livepatch.desktop.in
1new file mode 1006443new file mode 100644
index 0000000..c9f168a
--- /dev/null
+++ b/data/software-properties-livepatch.desktop.in
@@ -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;
diff --git a/debian/changelog b/debian/changelog
index a1449b1..1d7a3e8 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,15 @@
1software-properties (0.96.24.32.8) UNRELEASED; urgency=medium
2
3 * debian/control: Update Vcs-*: URLs
4 * debian/gbp.conf: Add default configuration
5 * Add .keep files to preserve empty directories
6 * Backport Livepatch changes from Disco (LP: #1823761):
7 - Implement new design for authentication dialog.
8 - Add livepatch desktop file and icon.
9 - Move Livepatch UI in a diffrent tab.
10
11 -- Andrea Azzarone <andrea.azzarone@canonical.com> Mon, 08 Apr 2019 16:52:15 +0100
12
1software-properties (0.96.24.32.7) bionic; urgency=medium13software-properties (0.96.24.32.7) bionic; urgency=medium
214
3 * SoftwarePropertiesGtk.py: when checking a package's depends for DKMS also15 * SoftwarePropertiesGtk.py: when checking a package's depends for DKMS also
diff --git a/debian/control b/debian/control
index fd8d887..cd54cef 100644
--- a/debian/control
+++ b/debian/control
@@ -7,13 +7,17 @@ Build-Depends: debhelper (>= 9),
7 libxml-parser-perl,7 libxml-parser-perl,
8 intltool,8 intltool,
9 dbus-x11 <!nocheck>,9 dbus-x11 <!nocheck>,
10 gir1.2-gtk-3.0 <!nocheck>,
10 gir1.2-snapd-1 <!nocheck>,11 gir1.2-snapd-1 <!nocheck>,
11 lsb-release <!nocheck>,12 lsb-release <!nocheck>,
12 pyflakes3 <!nocheck>,13 pyflakes3 <!nocheck>,
13 python3-apt <!nocheck>,14 python3-apt <!nocheck>,
15 python3-dateutil <!nocheck>,
14 python3-dbus <!nocheck>,16 python3-dbus <!nocheck>,
17 python3-distro-info <!nocheck>,
15 python3-gi <!nocheck>,18 python3-gi <!nocheck>,
16 python3-mock <!nocheck>,19 python3-mock <!nocheck>,
20 python3-requests-unixsocket <!nocheck>,
17 xauth <!nocheck>,21 xauth <!nocheck>,
18 xvfb <!nocheck>,22 xvfb <!nocheck>,
19 python3-all,23 python3-all,
@@ -22,7 +26,7 @@ Build-Depends: debhelper (>= 9),
22 dh-migrations,26 dh-migrations,
23 dh-translations27 dh-translations
24Standards-Version: 3.9.628Standards-Version: 3.9.6
25Vcs-Bzr: http://code.launchpad.net/~ubuntu-core-dev/software-properties/main29Vcs-Git: https://git.launchpad.net/software-properties -b ubuntu/bionic
26XS-Testsuite: autopkgtest30XS-Testsuite: autopkgtest
2731
28Package: python3-software-properties32Package: python3-software-properties
@@ -58,10 +62,11 @@ Depends: ${python3:Depends}, ${misc:Depends}, python3,
58 python3-gi,62 python3-gi,
59 gir1.2-gtk-3.0,63 gir1.2-gtk-3.0,
60 gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1),64 gir1.2-goa-1.0 (>= 3.27.92-1ubuntu1),
61 gir1.2-secret-1,
62 gir1.2-snapd-1,65 gir1.2-snapd-1,
63 python3-aptdaemon.gtk3widgets,66 python3-aptdaemon.gtk3widgets,
67 python3-dateutil,
64 python3-distro-info,68 python3-distro-info,
69 python3-requests-unixsocket,
65 software-properties-common,70 software-properties-common,
66 ubuntu-drivers-common (>= 1:0.2.75),71 ubuntu-drivers-common (>= 1:0.2.75),
67 python3-gi,72 python3-gi,
diff --git a/debian/gbp.conf b/debian/gbp.conf
68new file mode 10064473new file mode 100644
index 0000000..0bb5f1a
--- /dev/null
+++ b/debian/gbp.conf
@@ -0,0 +1,3 @@
1[DEFAULT]
2debian-branch = ubuntu/bionic
3debian-tag = %(version)s
diff --git a/debian/software-properties-gtk.install b/debian/software-properties-gtk.install
index c71d9d2..cfc4d82 100644
--- a/debian/software-properties-gtk.install
+++ b/debian/software-properties-gtk.install
@@ -5,6 +5,7 @@ debian/tmp/usr/share/mime/packages
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
diff --git a/po/POTFILES.in b/po/POTFILES.in
index d1917fd..47830e4 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -5,6 +5,7 @@ data/software-properties-gtk.desktop.in
5data/software-properties-gtk.appdata.xml.in5data/software-properties-gtk.appdata.xml.in
6data/software-properties-kde.desktop.in6data/software-properties-kde.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-kde10software-properties-kde
10add-apt-repository11add-apt-repository
@@ -28,8 +29,12 @@ softwareproperties/gtk/dialogs.py
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 @@ softwareproperties/AptAuth.py
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
diff --git a/setup.cfg b/setup.cfg
index ef8c2a0..9b28335 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -4,6 +4,7 @@ domain=software-properties
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-kde.desktop.in",),8 "data/software-properties-kde.desktop.in",),
8 )9 )
9 ]10 ]
diff --git a/softwareproperties/GoaAuth.py b/softwareproperties/GoaAuth.py
index 6583a4a..eb45bf7 100644
--- a/softwareproperties/GoaAuth.py
+++ b/softwareproperties/GoaAuth.py
@@ -21,7 +21,9 @@
2121
22import gi22import gi
23gi.require_version('Goa', '1.0')23gi.require_version('Goa', '1.0')
24from gi.repository import Gio, Goa, GObject24from gi.repository import Gio, GLib, Goa, GObject
25
26import logging
2527
26class GoaAuth(GObject.GObject):28class GoaAuth(GObject.GObject):
2729
@@ -32,12 +34,21 @@ class GoaAuth(GObject.GObject):
32 def __init__(self):34 def __init__(self):
33 GObject.GObject.__init__(self)35 GObject.GObject.__init__(self)
3436
35 self.goa_client = Goa.Client.new_sync(None)
36 self.account = None37 self.account = None
38 self.cancellable = Gio.Cancellable()
39 Goa.Client.new(self.cancellable, self._on_goa_client_ready)
3740
38 self.settings = Gio.Settings.new('com.ubuntu.SoftwareProperties')41 self.settings = Gio.Settings.new('com.ubuntu.SoftwareProperties')
39 self.settings.connect('changed::goa-account-id', self._on_settings_changed)42 self.settings.connect('changed::goa-account-id', self._on_settings_changed)
40 self._load()43
44 def _on_goa_client_ready(self, source, res):
45 try:
46 self.goa_client = Goa.Client.new_finish(res)
47 except GLib.Error as e:
48 logging.error('Failed to get a Gnome Online Account: {}'.format(e.message))
49 self.goa_client = None
50 else:
51 self._load()
4152
42 def login(self, account):53 def login(self, account):
43 assert(account)54 assert(account)
@@ -50,7 +61,7 @@ class GoaAuth(GObject.GObject):
5061
51 @GObject.Property62 @GObject.Property
52 def token(self):63 def token(self):
53 if self.account is None:64 if self.account is None or self.goa_client is None:
54 return None65 return None
5566
56 obj = self.goa_client.lookup_by_id(self.account.props.id)67 obj = self.goa_client.lookup_by_id(self.account.props.id)
@@ -64,7 +75,7 @@ class GoaAuth(GObject.GObject):
64 return pbased.call_get_password_sync('livepatch')75 return pbased.call_get_password_sync('livepatch')
6576
66 def _update_state_from_account_id(self, account_id):77 def _update_state_from_account_id(self, account_id):
67 if account_id:78 if account_id and self.goa_client is not None:
68 # Make sure the account-id is valid79 # Make sure the account-id is valid
69 obj = self.goa_client.lookup_by_id(account_id)80 obj = self.goa_client.lookup_by_id(account_id)
70 if obj is None:81 if obj is None:
diff --git a/softwareproperties/LivepatchService.py b/softwareproperties/LivepatchService.py
71new file mode 10064482new file mode 100644
index 0000000..694e64c
--- /dev/null
+++ b/softwareproperties/LivepatchService.py
@@ -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')
diff --git a/softwareproperties/LivepatchSnap.py b/softwareproperties/LivepatchSnap.py
0new file mode 100644255new file mode 100644
index 0000000..32b76be
--- /dev/null
+++ b/softwareproperties/LivepatchSnap.py
@@ -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, ''
diff --git a/softwareproperties/SoftwareProperties.py b/softwareproperties/SoftwareProperties.py
index 20d20e9..46ed68c 100644
--- a/softwareproperties/SoftwareProperties.py
+++ b/softwareproperties/SoftwareProperties.py
@@ -32,7 +32,6 @@ import re
32import os32import os
33import glob33import glob
34import shutil34import shutil
35import subprocess
36import threading35import threading
37import atexit36import atexit
38import tempfile37import tempfile
@@ -65,15 +64,8 @@ from . import shortcuts
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 @@ class SoftwareProperties(object):
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
@@ -874,147 +863,6 @@ class SoftwareProperties(object):
874 except:863 except:
875 return False864 return False
876865
877 #
878 # Livepatch
879 #
880 def init_snapd(self):
881 self.snapd_client = Snapd.Client()
882
883 def get_livepatch_snap_async(self, callback):
884 assert self.snapd_client
885 self.snapd_client.list_one_async('canonical-livepatch',
886 self.cancellable,
887 self.on_list_one_ready_cb,
888 callback)
889
890 def on_list_one_ready_cb(self, source_object, result, user_data):
891 callback = user_data
892 try:
893 snap = source_object.list_one_finish(result)
894 except:
895 snap = None
896 if snap:
897 if callback:
898 callback(snap)
899 return
900 else:
901 assert self.snapd_client
902 self.snapd_client.find_async(Snapd.FindFlags.MATCH_NAME,
903 'canonical-livepatch',
904 self.cancellable,
905 self.on_find_ready_cb,
906 callback)
907
908 def on_find_ready_cb(self, source_object, result, user_data):
909 callback = user_data
910 try:
911 snaps = source_object.find_finish(result)[0]
912 except:
913 snaps = list()
914 snap = snaps[0] if len(snaps) else None
915 if callback:
916 callback(snap)
917
918 def get_livepatch_snap_status(self, snap):
919 if snap is None:
920 return Snapd.SnapStatus.UNKNOWN
921 return snap.get_status()
922
923 # glib-snapd does not keep track of the status of the snap. Use this decorator
924 # to make it easy to write async functions that will always have an updated
925 # snap object.
926 def require_livepatch_snap(func):
927 def get_livepatch_snap_and_call(*args, **kwargs):
928 return args[0].get_livepatch_snap_async(lambda snap: func(snap=snap, *args, **kwargs))
929 return get_livepatch_snap_and_call
930
931 def is_livepatch_enabled(self):
932 file = Gio.File.new_for_path(path=self.LIVEPATCH_RUNNING_FILE)
933 return file.query_exists(None)
934
935 @require_livepatch_snap
936 def set_livepatch_enabled_async(self, enabled, token, callback, snap=None):
937 status = self.get_livepatch_snap_status(snap)
938 if status == Snapd.SnapStatus.UNKNOWN:
939 if callback:
940 callback(True, _("Canonical Livepatch snap cannot be installed."))
941 elif status == Snapd.SnapStatus.ACTIVE:
942 if enabled:
943 error = self.enable_livepatch_service(token)
944 else:
945 error = self.disable_livepatch_service()
946 if callback:
947 callback(len(error) > 0, error)
948 elif status == Snapd.SnapStatus.INSTALLED:
949 if enabled:
950 self.snapd_client.enable_async(name='canonical-livepatch',
951 cancellable=self.cancellable,
952 callback=self.livepatch_enable_snap_cb,
953 user_data=(callback, token))
954 else:
955 if callback:
956 callback(False, "")
957 elif status == Snapd.SnapStatus.AVAILABLE:
958 if enabled:
959 self.snapd_client.install_async(name='canonical-livepatch',
960 cancellable=self.cancellable,
961 callback=self.livepatch_install_snap_cb,
962 user_data=(callback, token))
963 else:
964 if callback:
965 callback(False, "")
966
967 def livepatch_enable_snap_cb(self, source_object, result, user_data):
968 (callback, token) = user_data
969 try:
970 if source_object.enable_finish(result):
971 error = self.enable_livepatch_service(token)
972 if callback:
973 callback(len(error) > 0, error)
974 except Exception:
975 if callback:
976 callback(True, _("Canonical Livepatch snap cannot be enabled."))
977
978 def livepatch_install_snap_cb(self, source_object, result, user_data):
979 (callback, token) = user_data
980 try:
981 if source_object.install_finish(result):
982 error = self.enable_livepatch_service(token)
983 if callback:
984 callback(len(error) > 0, error)
985 except Exception:
986 if callback:
987 callback(True, _("Canonical Livepatch snap cannot be installed."))
988
989 def enable_livepatch_service(self, token):
990 generic_error = _("Canonical Livepatch cannot be enabled.")
991
992 if self.is_livepatch_enabled():
993 return ""
994
995 try:
996 subprocess.check_output(['/snap/bin/canonical-livepatch', 'enable', token], stderr=subprocess.STDOUT)
997 return ""
998 except subprocess.CalledProcessError as e:
999 return e.output if e.output else generic_error
1000 except:
1001 return generic_error
1002
1003
1004 def disable_livepatch_service(self):
1005 generic_error = _("Canonical Livepatch cannot be disabled.")
1006
1007 if not self.is_livepatch_enabled():
1008 return ""
1009
1010 try:
1011 subprocess.check_output(['/snap/bin/canonical-livepatch', 'disable'], stderr=subprocess.STDOUT)
1012 return ""
1013 except subprocess.CalledProcessError as e:
1014 return e.output if e.output else generic_error
1015 except:
1016 return generic_error
1017
1018def shortcut_handler(shortcut):866def shortcut_handler(shortcut):
1019 for factory in _SHORTCUT_FACTORIES:867 for factory in _SHORTCUT_FACTORIES:
1020 ret = factory(shortcut)868 ret = factory(shortcut)
diff --git a/softwareproperties/dbus/SoftwarePropertiesDBus.py b/softwareproperties/dbus/SoftwarePropertiesDBus.py
index ace8733..2653cd8 100644
--- a/softwareproperties/dbus/SoftwarePropertiesDBus.py
+++ b/softwareproperties/dbus/SoftwarePropertiesDBus.py
@@ -25,10 +25,12 @@ import logging
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 @@ class SoftwarePropertiesDBus(dbus.service.Object, SoftwareProperties):
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):
@@ -324,9 +326,13 @@ class SoftwarePropertiesDBus(dbus.service.Object, SoftwareProperties):
324 sender_keyword="sender", connection_keyword="conn",326 sender_keyword="sender", connection_keyword="conn",
325 in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler'))327 in_signature='bs', out_signature='bs', async_callbacks=('reply_handler', 'error_handler'))
326 def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None):328 def SetLivepatchEnabled(self, enabled, token, reply_handler, error_handler, sender=None, conn=None):
329 def enable_thread_func():
330 ret = self._livepatch_service.set_enabled(enabled, token)
331 GLib.idle_add(lambda: reply_handler(*ret))
332
327 self._check_policykit_privilege(333 self._check_policykit_privilege(
328 sender, conn, "com.ubuntu.softwareproperties.applychanges")334 sender, conn, "com.ubuntu.softwareproperties.applychanges")
329 self.set_livepatch_enabled_async(enabled, token, reply_handler)335 threading.Thread(target=enable_thread_func).start()
330336
331 # helper from jockey337 # helper from jockey
332 def _check_policykit_privilege(self, sender, conn, privilege):338 def _check_policykit_privilege(self, sender, conn, privilege):
diff --git a/softwareproperties/gtk/DialogAuth.py b/softwareproperties/gtk/DialogAuth.py
index d62e518..421daef 100644
--- a/softwareproperties/gtk/DialogAuth.py
+++ b/softwareproperties/gtk/DialogAuth.py
@@ -19,6 +19,7 @@
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-130719# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
20# USA20# USA
2121
22from enum import IntEnum
22import os23import os
2324
24from gettext import gettext as _25from gettext import gettext as _
@@ -28,9 +29,13 @@ from softwareproperties.gtk.utils import (
2829
29import gi30import gi
30gi.require_version('Goa', '1.0')31gi.require_version('Goa', '1.0')
31from gi.repository import Gio, GLib, Goa, GObject, Gtk32from gi.repository import Gio, GLib, Goa, Gtk
32import logging33import logging
3334
35class Column(IntEnum):
36 ID = 0
37 MAIL = 1
38 ACCOUNT = 2
3439
35class DialogAuth:40class DialogAuth:
3641
@@ -38,68 +43,128 @@ class DialogAuth:
38 """setup up the gtk dialog"""43 """setup up the gtk dialog"""
39 self.parent = parent44 self.parent = parent
4045
41 setup_ui(self, os.path.join(datadir, "gtkbuilder",46 setup_ui(self, os.path.join(datadir, "gtkbuilder", "dialog-auth.ui"),
42 "dialog-auth.ui"), domain="software-properties")47 domain="software-properties")
43 self.label_title.set_max_width_chars(50)
4448
45 self.dialog = self.dialog_auth49 self.dialog = self.dialog_auth
46 self.dialog.use_header_bar = True50 self.dialog.set_title('')
51 self.dialog.set_deletable(False)
47 self.dialog.set_transient_for(parent)52 self.dialog.set_transient_for(parent)
4853
49 self.listboxrow_new_account.account = None54 self.button_continue.grab_focus()
5055
51 self.account = None56 self.account = None
52 self.dispose_on_new_account = False57 self.dispose_on_new_account = False
53 self.goa_client = Goa.Client.new_sync(None)58 self.goa_client = Goa.Client.new_sync(None)
5459
55 self.listbox_accounts.connect('row-activated', self._listbox_accounts_row_activated_cb)60 self._setup_model()
61 self._check_ui(select=False)
5662
57 # Be ready to other accounts63 # Be ready to other accounts
58 self.goa_client.connect('account-added', self._account_added_cb)64 self.goa_client.connect('account-added', self._account_added_cb)
59 self.goa_client.connect('account-removed', self._account_removed_cb)65 self.goa_client.connect('account-removed', self._account_removed_cb)
6066
61 self._setup_listbox_accounts()
62 self._check_ui()
63
64 def run(self):67 def run(self):
65 res = self.dialog.run()68 res = self.dialog.run()
66 self.dialog.hide()69 self.dialog.hide()
67 return res70 return res
6871
69 def _check_ui(self):72 def _setup_model(self):
70 rows = self.listbox_accounts.get_children()73 for obj in self.goa_client.get_accounts():
71 has_accounts = len(rows) > 174 self._add_account(obj.get_account(), select=False)
7275
73 if has_accounts:76 def _set_header(self, label):
74 title = _('To continue choose an Ubuntu Single Sign-On account.')77 self.label_header.set_markup(
75 new_account = _('Use another account…')78 "<span size='larger' weight='bold'>%s</span>" % label)
79
80 def _check_ui(self, select):
81 naccounts = len(self.liststore_account)
82
83 if naccounts == 0:
84 self._set_header(
85 _('To use Livepatch, you need to use an Ubuntu One account.'))
86 self.combobox_account.set_visible(False)
87 self.label_account.set_visible(False)
88 self.button_add_another.set_visible(False)
89 self.button_continue.set_label(_('Sign In / Register…'))
90 elif naccounts == 1:
91 self._set_header(
92 _('To use Livepatch, you need to use your Ubuntu One account.'))
93 self.combobox_account.set_visible(False)
94 self.label_account.set_visible(True)
95 self.label_account.set_text(self.liststore_account[0][Column.MAIL])
96 self.button_add_another.set_visible(True)
97 self.button_continue.set_label(_('Continue'))
76 else:98 else:
77 title = _('To continue you need an Ubuntu Single Sign-On account.')99 self._set_header(
78 new_account = _('Sign In…')100 _('To use Livepatch, you need to use an Ubuntu One account.'))
101 self.button_add_another.set_visible(True)
102 self.combobox_account.set_visible(True)
103 self.label_account.set_visible(False)
104 self.button_continue.set_label(_('Use'))
105 if select:
106 self.combobox_account.set_active(naccounts-1)
107 elif self.combobox_account.get_active() == -1:
108 self.combobox_account.set_active(0)
109
110 def _ignore_account(self, account):
111 return account.props.provider_type != 'ubuntusso'
112
113 def _get_account_iter(self, account):
114 row = self.liststore_account.get_iter_first()
115 while row is not None:
116 account_id = self.liststore_account.get_value(row, Column.ID)
117 if account_id == account.props.id:
118 return row
119 row = self.liststore_account.iter_next(row)
120 return None
121
122 def _add_account(self, account, select):
123 if self._ignore_account(account):
124 return
79125
80 self.label_title.set_text(title)126 account_iter = self._get_account_iter(account)
81 self.label_new_account.set_markup('<b>{}</b>'.format(new_account))127 if account_iter is not None:
128 return
82129
83 def _setup_listbox_accounts(self):130 account_iter = self.liststore_account.append()
84 for obj in self.goa_client.get_accounts():131 self.liststore_account.set(account_iter,
85 account = obj.get_account()132 [Column.ID, Column.MAIL, Column.ACCOUNT],
86 if self._is_account_supported(account):133 [account.props.id, account.props.presentation_identity, account])
87 self._add_account(account)134 self._check_ui(select)
88135
89 def _is_account_supported(self, account):136 def _remove_account(self, account):
90 return account.props.provider_type == 'ubuntusso'137 if self._ignore_account(account):
138 return
139
140 account_iter = self._get_account_iter(account)
141 if account_iter is None:
142 return
91143
92 def _add_account(self, account):144 self.liststore_account.remove(account_iter)
93 row = self._create_row(account)145 self._check_ui(select=False)
94 self.listbox_accounts.prepend(row)
95 self._check_ui()
96146
97 def _remove_account(self, account):147 def _response_if_valid(self, account):
98 for row in self.listbox_accounts.get_children():148 def cb(source, res, data):
99 if row.account == account:149 try:
100 row.destroy()150 source.call_ensure_credentials_finish(res)
101 self._check_ui()151 valid = True
102 break152 except GLib.Error as e:
153 logging.warning("call_ensure_credentials_finish exception: %s",
154 e.message)
155 valid = False
156
157 if not valid:
158 try:
159 self._spawn_goa_with_args(account.props.id, None)
160 except GLib.Error as e:
161 logging.warning ('Failed to spawn gnome-control-center: %s',
162 e.message)
163 else:
164 self.account = account
165 self.dialog.response(Gtk.ResponseType.OK)
166
167 account.call_ensure_credentials(None, cb, None)
103168
104 def _build_dbus_params(self, action, arg):169 def _build_dbus_params(self, action, arg):
105 builder = GLib.VariantBuilder.new(GLib.VariantType.new('av'))170 builder = GLib.VariantBuilder.new(GLib.VariantType.new('av'))
@@ -118,7 +183,8 @@ class DialogAuth:
118 v = GLib.Variant.new_variant(s)183 v = GLib.Variant.new_variant(s)
119 builder.add_value(v)184 builder.add_value(v)
120185
121 array = GLib.Variant.new_tuple(GLib.Variant.new_string('online-accounts'), builder.end())186 array = GLib.Variant.new_tuple(
187 GLib.Variant.new_string('online-accounts'), builder.end())
122 array = GLib.Variant.new_variant(array)188 array = GLib.Variant.new_variant(array)
123189
124 param = GLib.Variant.new_tuple(190 param = GLib.Variant.new_tuple(
@@ -135,94 +201,43 @@ class DialogAuth:
135 'org.gtk.Actions', None)201 'org.gtk.Actions', None)
136202
137 param = self._build_dbus_params(action, arg)203 param = self._build_dbus_params(action, arg)
138 timeout = 10*60*1000 # 10 minutes should be enough to create an account204 proxy.call_sync('Activate', param, Gio.DBusCallFlags.NONE, -1, None)
139 proxy.call_sync('Activate', param, Gio.DBusCallFlags.NONE, timeout, None)
140
141 def _create_row(self, account):
142 identity = account.props.presentation_identity
143 provider_name = account.props.provider_name
144
145 row = Gtk.ListBoxRow.new()
146 row.show()
147
148 hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)
149 hbox.set_hexpand(True)
150 hbox.show()
151
152 image = Gtk.Image.new_from_icon_name('avatar-default', Gtk.IconSize.DIALOG)
153 image.set_pixel_size(48)
154 image.show()
155 hbox.pack_start(image, False, False, 0)
156
157 vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 2)
158 vbox.set_valign(Gtk.Align.CENTER)
159 vbox.show()
160 hbox.pack_start(vbox, False, False, 0)
161
162 if identity:
163 ilabel = Gtk.Label.new()
164 ilabel.set_halign(Gtk.Align.START)
165 ilabel.set_markup('<b>{}</b>'.format(identity))
166 ilabel.show()
167 vbox.pack_start(ilabel, True, True, 0)
168
169 if provider_name:
170 plabel = Gtk.Label.new()
171 plabel.set_halign(Gtk.Align.START)
172 plabel.set_markup('<small><span foreground=\"#555555\">{}</span></small>'.format(provider_name))
173 plabel.show()
174 vbox.pack_start(plabel, True, True, 0)
175
176 warning_icon = Gtk.Image.new_from_icon_name('dialog-warning-symbolic', Gtk.IconSize.BUTTON)
177 warning_icon.set_no_show_all(True)
178 warning_icon.set_margin_end (15)
179 hbox.pack_end(warning_icon, False, False, 0)
180
181 row.add(hbox)
182
183 account.bind_property('attention-needed', warning_icon, 'visible',
184 GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE)
185
186 row.account = account
187 return row
188
189 # Signals handlers
190 def _listbox_accounts_row_activated_cb(self, listbox, row):
191 account = row.account
192
193 if account is None:
194 # TODO (azzar1): there is no easy way to put this to false
195 # if the user close the windows without adding an account.
196 # We need to discuss with goa's upstream to support such usercases
197 try:
198 self._spawn_goa_with_args('add', 'ubuntusso')
199 self.dispose_on_new_account = True
200 except GLib.Error as e:
201 logging.warning ('Failed to spawing gnome-control-center: %s', e.message)
202 else:
203 if account.props.attention_needed:
204 try:
205 self._spawn_goa_with_args(account.props.id, None)
206 except GLib.Error as e:
207 logging.warning ('Failed to spawing gnome-control-center: %s', e.message)
208 else:
209 self.account = account
210 self.dialog.response(Gtk.ResponseType.OK)
211205
212 def _account_added_cb(self, goa_client, goa_object):206 def _account_added_cb(self, goa_client, goa_object):
213 account = goa_object.get_account()207 account = goa_object.get_account()
214 if not self._is_account_supported(account):208 if self._ignore_account(account):
215 return209 return
216 if not self.dispose_on_new_account:210 if not self.dispose_on_new_account:
217 self._add_account(account)211 self._add_account(account, True)
218 else:212 else:
219 self.account = account213 self._response_if_valid(account)
220 self.dialog.response(Gtk.ResponseType.OK)
221214
222 def _account_removed_cb(self, goa_client, goa_object):215 def _account_removed_cb(self, goa_client, goa_object):
223 account = goa_object.get_account()216 account = goa_object.get_account()
224 if self._is_account_supported(account):217 if not self._ignore_account(account):
225 self._remove_account(account)218 self._remove_account(account)
226219
220 def _button_add_another_clicked_cb(self, button):
221 try:
222 # There is no easy way to put this to false if the user close the
223 # windows without adding an account.
224 self._spawn_goa_with_args('add', 'ubuntusso')
225 self.dispose_on_new_account = True
226 except GLib.Error as e:
227 logging.warning ('Failed to spawn control-center: %s', e.message)
228
229 def _button_cancel_clicked_cb(self, button):
230 self.dialog.response(Gtk.ResponseType.CANCEL)
231
232 def _button_continue_clicked_cb(self, button):
233 naccounts = len(self.liststore_account)
227234
235 account = None
236 if naccounts >= 1:
237 active_index = self.combobox_account.get_active()
238 account = self.liststore_account[active_index][Column.ACCOUNT]
228239
240 if account is None:
241 self._button_add_another_clicked_cb(self.button_add_another)
242 else:
243 self._response_if_valid(account)
diff --git a/softwareproperties/gtk/DialogLivepatchError.py b/softwareproperties/gtk/DialogLivepatchError.py
index 2d6688c..e27bb69 100644
--- a/softwareproperties/gtk/DialogLivepatchError.py
+++ b/softwareproperties/gtk/DialogLivepatchError.py
@@ -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 @@ class DialogLivepatchError:
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()
diff --git a/softwareproperties/gtk/LivepatchPage.py b/softwareproperties/gtk/LivepatchPage.py
51new file mode 10064460new file mode 100644
index 0000000..6c3ace9
--- /dev/null
+++ b/softwareproperties/gtk/LivepatchPage.py
@@ -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 is not None or
177 (availability == LivepatchAvailability.FALSE and
178 availability_message is not None))
179
180 if has_error:
181 self._parent.stack_livepatch.set_visible_child_name('page_livepatch_message')
182 self._parent.stack_livepatch.set_visible(True)
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_child_name('page_livepatch_status')
236 self._parent.stack_livepatch.set_visible(True)
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))
diff --git a/softwareproperties/gtk/SimpleGtkbuilderApp.py b/softwareproperties/gtk/SimpleGtkbuilderApp.py
index 06375ff..e14f840 100644
--- a/softwareproperties/gtk/SimpleGtkbuilderApp.py
+++ b/softwareproperties/gtk/SimpleGtkbuilderApp.py
@@ -38,6 +38,9 @@ class SimpleGtkbuilderApp(Gtk.Application):
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.
diff --git a/softwareproperties/gtk/SoftwarePropertiesGtk.py b/softwareproperties/gtk/SoftwarePropertiesGtk.py
index 75ff85d..9271f54 100644
--- a/softwareproperties/gtk/SoftwarePropertiesGtk.py
+++ b/softwareproperties/gtk/SoftwarePropertiesGtk.py
@@ -27,9 +27,6 @@ from __future__ import absolute_import, print_function
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 @@ from .DialogMirror import DialogMirror
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 @@ RESPONSE_ADD = 2
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 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
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,163 +1478,5 @@ class SoftwarePropertiesGtk(SoftwareProperties, SimpleGtkbuilderApp):
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 = False
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 return self.has_online_accounts() and \
1526 di.is_lts(distro.codename) and \
1527 distro.codename in di.supported(datetime.now().date())
1528
1529 def on_goa_auth_changed(self):
1530 if self.goa_auth.logged:
1531 self.button_ubuntuone.set_label(_('Sign Out'))
1532
1533 if self.goa_auth.token:
1534 self.checkbutton_livepatch.set_sensitive(True)
1535 self.label_livepatch_login.set_label(_('Signed in as %s' % self.goa_auth.username))
1536 else:
1537 self.checkbutton_livepatch.set_sensitive(False)
1538 text = _('%s isn\'t authorized to use Livepatch.' % self.goa_auth.username)
1539 text = "<span color='red'>" + text + "</span>"
1540 self.label_livepatch_login.set_markup(text)
1541 else:
1542 if self.is_livepatch_enabled() and not self.waiting_livepatch_response:
1543 # Allow the user to disable livepatch even if
1544 # the account expired (see LP: #1768797)
1545 self.checkbutton_livepatch.set_sensitive(True)
1546 self.label_livepatch_login.set_label(_('Livepatch is active.'))
1547 else:
1548 self.checkbutton_livepatch.set_sensitive(False)
1549 self.label_livepatch_login.set_label(_('To use Livepatch you need to sign in.'))
1550
1551 self.button_ubuntuone.set_label(_('Sign In…'))
1552
1553 def on_livepatch_status_changed(self, file_monitor, file, other_file, event_type):
1554 if not self.waiting_livepatch_response:
1555 self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1556 self.on_goa_auth_changed()
1557
1558 def on_button_ubuntuone_clicked(self, button):
1559 if self.goa_auth.logged:
1560 self.do_logout()
1561 else:
1562 self.do_login()
1563
1564 def do_login(self):
1565 try:
1566 # Show login dialog!
1567 dialog = DialogAuth(self.window_main, self.datadir)
1568 response = dialog.run()
1569 except Exception as e:
1570 logging.error(e)
1571 error(self.window_main,
1572 _("Error enabling Canonical Livepatch"),
1573 _("Please check your Internet connection."))
1574 else:
1575 if response == Gtk.ResponseType.OK:
1576 self.goa_auth.login(dialog.account)
1577 if self.goa_auth.logged:
1578 self.checkbutton_livepatch.set_active(True)
1579
1580 def do_logout(self):
1581 self.checkbutton_livepatch.set_active(False)
1582 self.goa_auth.logout()
1583
1584 def on_checkbutton_livepatch_toggled(self, checkbutton):
1585 if self.waiting_livepatch_response:
1586 return
1587
1588 self.waiting_livepatch_response = True
1589
1590 token = ''
1591 enabled = False
1592 if self.checkbutton_livepatch.get_active():
1593 enabled = True
1594 token = self.goa_auth.token if self.goa_auth.token else ''
1595 self.backend.SetLivepatchEnabled(enabled, token,
1596 reply_handler=self.livepatch_enabled_reply_handler,
1597 error_handler=self.livepatch_enabled_error_handler,
1598 timeout=LIVEPATCH_TIMEOUT)
1599
1600 def livepatch_enabled_reply_handler(self, is_error, prompt):
1601 self.sync_checkbutton_livepatch(is_error, prompt)
1602
1603 def livepatch_enabled_error_handler(self, e):
1604 if e._dbus_error_name == 'com.ubuntu.SoftwareProperties.PermissionDeniedByPolicy':
1605 logging.error("Authentication canceled, changes have not been saved")
1606 self.sync_checkbutton_livepatch(is_error=True, prompt=None)
1607 else:
1608 self.sync_checkbutton_livepatch(is_error=True, prompt=str(e))
1609
1610 def sync_checkbutton_livepatch(self, is_error, prompt):
1611 if is_error:
1612 self.waiting_livepatch_response = False
1613 self.checkbutton_livepatch.handler_block(self.handlers[self.checkbutton_livepatch])
1614 self.checkbutton_livepatch.set_active(self.is_livepatch_enabled())
1615 self.checkbutton_livepatch.handler_unblock(self.handlers[self.checkbutton_livepatch])
1616
1617 if prompt:
1618 dialog = DialogLivepatchError(self.window_main, self.datadir)
1619 response = dialog.run(prompt, show_settings_button=self.quit_when_livepatch_responds)
1620 if response == DialogLivepatchError.RESPONSE_SETTINGS:
1621 self.window_main.show()
1622 self.quit_when_livepatch_responds = False
1623 else:
1624 do_dbus_call = False
1625 if self.is_livepatch_enabled() and not self.checkbutton_livepatch.get_active():
1626 do_dbus_call = True
1627 enabled = False
1628 token = ''
1629 elif not self.is_livepatch_enabled() and self.checkbutton_livepatch.get_active():
1630 do_dbus_call = True
1631 enabled = True
1632 token = self.goa_auth.token if self.goa_auth.token else ''
1633 else:
1634 self.waiting_livepatch_response = False
1635
1636 if do_dbus_call:
1637 self.backend.SetLivepatchEnabled(enabled, token,
1638 reply_handler=self.livepatch_enabled_reply_handler,
1639 error_handler=self.livepatch_enabled_error_handler,
1640 timeout=LIVEPATCH_TIMEOUT)
1641
1642 self.on_goa_auth_changed()
1643
1644 if self.quit_when_livepatch_responds:
1645 self.on_close_button(self.button_close)
diff --git a/softwareproperties/gtk/utils.py b/softwareproperties/gtk/utils.py
index 37f0c10..e0ddca9 100644
--- a/softwareproperties/gtk/utils.py
+++ b/softwareproperties/gtk/utils.py
@@ -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 @@ def setup_ui(self, path, domain):
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
diff --git a/tests/aptroot/etc/apt/apt.conf.d/.keep b/tests/aptroot/etc/apt/apt.conf.d/.keep
40new file mode 10064495new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/aptroot/etc/apt/apt.conf.d/.keep

Subscribers

People subscribed via source and target branches