2
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
4
# Copyright (c) 2012 Canonical
6
# Author: Michael Terry <michael.terry@canonical.com>
8
# This program is free software; you can redistribute it and/or
9
# modify it under the terms of the GNU General Public License as
10
# published by the Free Software Foundation; either version 2 of the
11
# License, or (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program; if not, write to the Free Software
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
23
from __future__ import absolute_import, print_function
25
from gi.repository import Gtk
26
from gi.repository import Gdk, GdkX11
27
from gi.repository import Gio
28
from gi.repository import GLib
29
from gi.repository import GObject
34
warnings.filterwarnings("ignore", "Accessed deprecated property",
43
from gettext import gettext as _
44
from uaclient.api.u.pro.packages.updates.v1 import updates
48
from dbus.mainloop.glib import DBusGMainLoop
49
DBusGMainLoop(set_as_default=True)
51
from .UnitySupport import UnitySupport
52
from .Dialogs import (DistUpgradeDialog,
57
NoUpgradeForYouDialog,
62
from .MetaReleaseGObject import MetaRelease
63
from .UpdatesAvailable import UpdatesAvailable
64
from .Core.AlertWatcher import AlertWatcher
65
from .Core.MyCache import MyCache
66
from .Core.roam import NetworkManagerHelper
67
from .Core.UpdateList import UpdateList
68
from .Core.utils import get_arch, get_dist
69
from .backend import (InstallBackend,
72
# file that signals if we need to reboot
73
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
76
class UpdateManager(Gtk.Window):
77
""" This class is the main window and work flow controller. The main
78
window will show panes, and it will morph between them. """
80
def __init__(self, datadir, options):
81
Gtk.Window.__init__(self)
84
self.datadir = datadir
85
self.options = options
86
self.unity = UnitySupport()
87
self.controller = None
89
self.ua_security_packages = []
90
self.update_list = None
91
self.meta_release = None
92
self.hwe_replacement_packages = None
93
self.oem_metapackages = set()
94
self.duplicate_packages = []
95
self.arch = get_arch()
97
# Basic GTK+ parameters
98
self.set_title(_("Software Updater"))
99
self.set_icon_name("system-software-update")
100
self.set_position(Gtk.WindowPosition.CENTER)
102
# Keep window at a constant size
103
ctx = self.get_style_context()
104
self.style_changed = ctx.connect("changed",
106
self.resize_to_standard_width())
109
self.connect("delete-event", self._on_close)
113
# deal with no-focus-on-map
114
if self.options and self.options.no_focus_on_map:
115
self.set_focus_on_map(False)
118
self.set_urgency_hint(True)
119
self.unity.set_urgency(True)
120
self.initial_focus_id = self.connect(
121
"focus-in-event", self.on_initial_focus_in)
123
# Look for a new release in a thread
124
self.meta_release = MetaRelease(
125
self.options and self.options.devel_release,
126
self.options and self.options.use_proposed,
127
self.options and self.options.debug)
129
def begin_user_resizable(self, stored_width=0, stored_height=0):
130
self.set_resizable(True)
131
if stored_width > 0 and stored_height > 0:
132
# There is a race here. If we immediately resize, it often doesn't
133
# take. Using idle_add helps, but we *still* occasionally don't
134
# restore the size correctly. Help needed to track this down!
135
GLib.idle_add(lambda: self.resize(stored_width, stored_height))
137
def end_user_resizable(self):
138
self.set_resizable(False)
140
def resize_to_standard_width(self):
141
if self.get_resizable():
142
return # only size to a specific em if we are a static size
143
num_em = 33 # per SoftwareUpdates spec
144
dpi = self.get_screen().get_resolution()
147
ctx = self.get_style_context()
148
GObject.signal_handler_block(ctx, self.style_changed)
149
size = ctx.get_property("font-size", Gtk.StateFlags.NORMAL)
150
width = dpi / 72 * size * num_em
151
self.set_size_request(width, -1)
152
GObject.signal_handler_unblock(ctx, self.style_changed)
154
def on_initial_focus_in(self, widget, event):
155
"""callback run on initial focus-in (if started unmapped)"""
157
self.set_urgency_hint(False)
158
self.unity.set_urgency(False)
159
self.disconnect(self.initial_focus_id)
162
def _start_pane(self, pane):
163
if self.controller is not None:
164
self.controller.stop()
165
if isinstance(self.controller, Gtk.Widget):
166
self.controller.destroy()
168
self.controller = pane
170
self.end_user_resizable()
175
if isinstance(pane, Gtk.Widget):
183
def _on_close(self, widget, data=None):
187
if not self.get_sensitive():
191
controller_close = self.controller.close()
193
return controller_close
197
""" exit the application, save the state """
198
self._start_pane(None)
201
def show_settings(self):
202
cmd = ["/usr/bin/software-properties-gtk",
205
if "WAYLAND_DISPLAY" not in os.environ:
206
cmd += ["--toplevel", "%s" % self.get_window().get_xid()]
210
p = subprocess.Popen(cmd)
214
while p.poll() is None:
215
while Gtk.events_pending():
219
self.start_available()
221
def start_update(self):
222
if self.options.no_update:
223
self.start_available()
226
update_backend = get_backend(self, InstallBackend.ACTION_UPDATE)
227
self._start_pane(update_backend)
229
def start_install(self, hwe_upgrade=False):
230
install_backend = get_backend(self, InstallBackend.ACTION_PRE_INSTALL)
232
for pkgname in self.hwe_replacement_packages:
234
self.cache[pkgname].mark_install()
237
self._start_pane(install_backend)
239
def start_available(self, cancelled_update=False, error_occurred=False):
243
if self.cache is None:
246
pane = self._make_available_pane(self.cache.install_count
247
+ self.cache.del_count,
248
os.path.exists(REBOOT_REQUIRED_FILE),
249
cancelled_update, error_occurred)
250
self._start_pane(pane)
252
def _check_oem_metapackages(self):
253
di = distro_info.UbuntuDistroInfo()
254
codename = get_dist()
255
lts = di.is_lts(codename)
258
OEM_PATH = os.path.join(GLib.get_user_runtime_dir(),
259
"ubuntu-drivers-oem.package-list")
260
if not os.path.exists(OEM_PATH):
263
# Packages that aren't installed but apply to this system
264
with open(OEM_PATH, 'r') as f:
265
self.oem_metapackages |= set(f.read().splitlines())
267
# Packages that are already installed
268
for pkg in self.cache:
269
if fnmatch.fnmatch(pkg.name, 'oem-*-meta') \
271
self.oem_metapackages.add(pkg)
273
def _get_ua_security_status(self):
274
self.ua_security_packages = []
275
update_result = updates()
276
for info in update_result.updates:
277
if info.status == 'pending_attach':
278
self.ua_security_packages.append((info.name, info.version, info.download_size))
280
def _make_available_pane(self, install_count, need_reboot=False,
281
cancelled_update=False, error_occurred=False):
282
self._check_hwe_support_status()
283
if install_count == 0:
284
# Need Restart > New Release > No Updates
286
return NeedRestartDialog(self)
287
dist_upgrade = self._check_meta_release()
290
elif cancelled_update:
291
return StoppedUpdatesDialog(self)
292
elif self.hwe_replacement_packages:
293
return HWEUpgradeDialog(self)
295
return NoUpdatesDialog(self, error_occurred=error_occurred)
300
desc = _("Some software couldn’t be checked for updates.")
301
elif cancelled_update:
302
header = _("You stopped the check for updates.")
303
desc = _("Updated software is available from "
305
# Display HWE updates first as an old HWE stack is vulnerable
306
elif self.hwe_replacement_packages:
307
return HWEUpgradeDialog(self)
308
return UpdatesAvailable(self, header, desc, need_reboot)
310
def start_error(self, update_and_retry, header, desc):
312
self._start_pane(UpdateErrorDialog(self, header, desc))
314
self._start_pane(ErrorDialog(self, header, desc))
316
def _look_busy(self):
317
self.set_sensitive(False)
318
if self.get_window() is not None:
319
self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH))
321
def _look_ready(self):
322
self.set_sensitive(True)
323
if self.get_window() is not None:
324
self.get_window().set_cursor(None)
325
self.get_window().set_functions(Gdk.WMFunction.ALL)
327
def _check_meta_release(self):
328
if self.meta_release is None:
331
if self.meta_release.downloading:
332
# Block until we get an answer
333
GLib.idle_add(self._meta_release_wait_idle)
336
# Check if there is anything to upgrade to or a known-broken upgrade
337
next = self.meta_release.upgradable_to
338
if not next or next.upgrade_broken:
341
# Check for end-of-life
342
if self.meta_release.no_longer_supported:
343
return UnsupportedDialog(self, self.meta_release)
345
# Check for new fresh release
346
settings = Gio.Settings.new("com.ubuntu.update-manager")
347
if (self.meta_release.new_dist
348
and (self.options.check_dist_upgrades
349
or settings.get_boolean("check-dist-upgrades"))):
350
if self.arch == 'i386':
351
return NoUpgradeForYouDialog(self, self.meta_release,
353
return DistUpgradeDialog(self, self.meta_release)
357
def _meta_release_wait_idle(self):
358
# 'downloading' is changed in a thread, but the signal
359
# 'done_downloading' is done in our thread's event loop. So we know
360
# that it won't fire while we're in this function.
361
if not self.meta_release.downloading:
364
self.meta_release.connect("done_downloading", Gtk.main_quit)
367
def _check_hwe_support_status(self):
368
di = distro_info.UbuntuDistroInfo()
369
codename = get_dist()
370
lts = di.is_lts(codename)
373
HWE = "/usr/bin/hwe-support-status"
374
if not os.path.exists(HWE):
376
cmd = [HWE, "--show-replacements"]
377
self._parse_hwe_support_status(cmd)
379
def _parse_hwe_support_status(self, cmd):
381
subprocess.check_output(cmd)
383
# print("nothing unsupported running")
384
except subprocess.CalledProcessError as e:
385
if e.returncode == 10:
386
packages = e.output.strip().split()
387
self.hwe_replacement_packages = []
388
for pkgname in packages:
389
pkgname = pkgname.decode('utf-8')
390
if pkgname in self.cache and \
391
not self.cache[pkgname].is_installed:
392
self.hwe_replacement_packages.append(pkgname)
394
# print(self.hwe_replacement_packages)
396
# fixme: we should probably abstract away all the stuff from libapt
397
def refresh_cache(self):
399
if self.cache is None:
400
self.cache = MyCache(None)
402
self.cache.open(None)
403
self.cache._initDepCache()
404
except AssertionError:
405
# if the cache could not be opened for some reason,
406
# let the release upgrader handle it, it deals
407
# a lot better with this
408
self._start_pane(PartialUpgradeDialog(self))
409
# we assert a clean cache
410
header = _("Software index is broken")
411
desc = _("It is impossible to install or remove any software. "
412
"Please use the package manager \"Synaptic\" or run "
413
"\"sudo apt-get install -f\" in a terminal to fix "
414
"this issue at first.")
415
self.start_error(False, header, desc)
417
except SystemError as e:
418
header = _("Could not initialize the package information")
419
desc = _("An unresolvable problem occurred while "
420
"initializing the package information.\n\n"
421
"Please report this bug against the 'update-manager' "
422
"package and include the following error "
423
"message:\n") + str(e)
424
self.start_error(False, header, desc)
427
# Let the Gtk event loop breath if it hasn't had a chance.
429
while Gtk.events_pending():
433
self._check_oem_metapackages()
435
self._get_ua_security_status()
437
for pkgname in self.oem_metapackages:
439
if not self.cache[pkgname].is_installed:
440
self.cache[pkgname].mark_install()
444
self.update_list = UpdateList(self)
446
self.update_list.update(self.cache, eventloop_callback=iterate,
447
duplicate_packages=self.duplicate_packages,
448
ua_security_packages=self.
449
ua_security_packages)
450
except SystemError as e:
451
header = _("Could not calculate the upgrade")
452
desc = _("An unresolvable problem occurred while "
453
"calculating the upgrade.\n\n"
454
"Please report this bug against the 'update-manager' "
455
"package and include the following error "
456
"message:\n") + str(e)
457
self.start_error(True, header, desc)
459
if self.update_list.distUpgradeWouldDelete > 0:
460
self._start_pane(PartialUpgradeDialog(self))
462
def _setup_dbus(self):
463
""" this sets up a dbus listener if none is installed already """
464
# check if there is another g-a-i already and if not setup one
467
bus = dbus.SessionBus()
469
print("warning: could not initiate dbus")
472
proxy_obj = bus.get_object('org.freedesktop.UpdateManager',
473
'/org/freedesktop/UpdateManagerObject')
474
iface = dbus.Interface(proxy_obj,
475
'org.freedesktop.UpdateManagerIFace')
477
#print("send bringToFront")
479
except dbus.DBusException:
480
#print("no listening object (%s) " % e)
481
bus_name = dbus.service.BusName('org.freedesktop.UpdateManager',
483
self.dbusController = UpdateManagerDbusController(self, bus_name)
486
class UpdateManagerDbusController(dbus.service.Object):
487
""" this is a helper to provide the UpdateManagerIFace """
488
def __init__(self, parent, bus_name,
489
object_path='/org/freedesktop/UpdateManagerObject'):
490
dbus.service.Object.__init__(self, bus_name, object_path)
492
self.alert_watcher = AlertWatcher()
493
self.alert_watcher.connect("network-alert", self._on_network_alert)
494
self.connected = False
496
@dbus.service.method('org.freedesktop.UpdateManagerIFace')
497
def bringToFront(self):
498
self.parent.present()
501
@dbus.service.method('org.freedesktop.UpdateManagerIFace')
504
self.parent.start_install()
509
def _on_network_alert(self, watcher, state):
510
if state in NetworkManagerHelper.NM_STATE_CONNECTED_LIST:
511
self.connected = True
513
self.connected = False