~ubuntu-core-dev/update-manager/main

« back to all changes in this revision

Viewing changes to UpdateManager/UpdateManager.py

  • Committer: Michael Vogt
  • Date: 2005-11-15 14:44:23 UTC
  • Revision ID: egon@top-20051115144423-5d01f44d63b99ecc
* build-system tweaks

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# UpdateManager.py
2
 
# -*- Mode: Python; indent-tabs-mode: nil; tab-width: 4; coding: utf-8 -*-
3
 
#
4
 
#  Copyright (c) 2012 Canonical
5
 
#
6
 
#  Author: Michael Terry <michael.terry@canonical.com>
7
 
#
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.
12
 
#
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.
17
 
#
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
21
 
#  USA
22
 
 
23
 
from __future__ import absolute_import, print_function
24
 
 
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
30
 
 
31
 
GdkX11  # pyflakes
32
 
 
33
 
import warnings
34
 
warnings.filterwarnings("ignore", "Accessed deprecated property",
35
 
                        DeprecationWarning)
36
 
 
37
 
import distro_info
38
 
import fnmatch
39
 
import os
40
 
import subprocess
41
 
import sys
42
 
import time
43
 
from gettext import gettext as _
44
 
from uaclient.api.u.pro.packages.updates.v1 import updates
45
 
 
46
 
import dbus
47
 
import dbus.service
48
 
from dbus.mainloop.glib import DBusGMainLoop
49
 
DBusGMainLoop(set_as_default=True)
50
 
 
51
 
from .UnitySupport import UnitySupport
52
 
from .Dialogs import (DistUpgradeDialog,
53
 
                      ErrorDialog,
54
 
                      HWEUpgradeDialog,
55
 
                      NeedRestartDialog,
56
 
                      NoUpdatesDialog,
57
 
                      NoUpgradeForYouDialog,
58
 
                      PartialUpgradeDialog,
59
 
                      StoppedUpdatesDialog,
60
 
                      UnsupportedDialog,
61
 
                      UpdateErrorDialog)
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,
70
 
                      get_backend)
71
 
 
72
 
# file that signals if we need to reboot
73
 
REBOOT_REQUIRED_FILE = "/var/run/reboot-required"
74
 
 
75
 
 
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. """
79
 
 
80
 
    def __init__(self, datadir, options):
81
 
        Gtk.Window.__init__(self)
82
 
 
83
 
        # Public members
84
 
        self.datadir = datadir
85
 
        self.options = options
86
 
        self.unity = UnitySupport()
87
 
        self.controller = None
88
 
        self.cache = 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()
96
 
 
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)
101
 
 
102
 
        # Keep window at a constant size
103
 
        ctx = self.get_style_context()
104
 
        self.style_changed = ctx.connect("changed",
105
 
                                         lambda ctx:
106
 
                                             self.resize_to_standard_width())
107
 
 
108
 
        # Signals
109
 
        self.connect("delete-event", self._on_close)
110
 
 
111
 
        self._setup_dbus()
112
 
 
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)
116
 
            self.iconify()
117
 
            self.stick()
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)
122
 
 
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)
128
 
 
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))
136
 
 
137
 
    def end_user_resizable(self):
138
 
        self.set_resizable(False)
139
 
 
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()
145
 
        if dpi <= 0:
146
 
            dpi = 96
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)
153
 
 
154
 
    def on_initial_focus_in(self, widget, event):
155
 
        """callback run on initial focus-in (if started unmapped)"""
156
 
        self.unstick()
157
 
        self.set_urgency_hint(False)
158
 
        self.unity.set_urgency(False)
159
 
        self.disconnect(self.initial_focus_id)
160
 
        return False
161
 
 
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()
167
 
 
168
 
        self.controller = pane
169
 
        self._look_ready()
170
 
        self.end_user_resizable()
171
 
 
172
 
        if pane is None:
173
 
            return
174
 
 
175
 
        if isinstance(pane, Gtk.Widget):
176
 
            self.add(pane)
177
 
            pane.start()
178
 
            self.show_all()
179
 
        else:
180
 
            pane.start()
181
 
            self.hide()
182
 
 
183
 
    def _on_close(self, widget, data=None):
184
 
        return self.close()
185
 
 
186
 
    def close(self):
187
 
        if not self.get_sensitive():
188
 
            return True
189
 
 
190
 
        if self.controller:
191
 
            controller_close = self.controller.close()
192
 
            if controller_close:
193
 
                return controller_close
194
 
        self.exit()
195
 
 
196
 
    def exit(self):
197
 
        """ exit the application, save the state """
198
 
        self._start_pane(None)
199
 
        sys.exit(0)
200
 
 
201
 
    def show_settings(self):
202
 
        cmd = ["/usr/bin/software-properties-gtk",
203
 
               "--open-tab", "2"]
204
 
 
205
 
        if "WAYLAND_DISPLAY" not in os.environ:
206
 
            cmd += ["--toplevel", "%s" % self.get_window().get_xid()]
207
 
 
208
 
        self._look_busy()
209
 
        try:
210
 
            p = subprocess.Popen(cmd)
211
 
        except OSError:
212
 
            pass
213
 
        else:
214
 
            while p.poll() is None:
215
 
                while Gtk.events_pending():
216
 
                    Gtk.main_iteration()
217
 
                time.sleep(0.05)
218
 
        finally:
219
 
            self.start_available()
220
 
 
221
 
    def start_update(self):
222
 
        if self.options.no_update:
223
 
            self.start_available()
224
 
            return
225
 
 
226
 
        update_backend = get_backend(self, InstallBackend.ACTION_UPDATE)
227
 
        self._start_pane(update_backend)
228
 
 
229
 
    def start_install(self, hwe_upgrade=False):
230
 
        install_backend = get_backend(self, InstallBackend.ACTION_PRE_INSTALL)
231
 
        if hwe_upgrade:
232
 
            for pkgname in self.hwe_replacement_packages:
233
 
                try:
234
 
                    self.cache[pkgname].mark_install()
235
 
                except SystemError:
236
 
                    pass
237
 
        self._start_pane(install_backend)
238
 
 
239
 
    def start_available(self, cancelled_update=False, error_occurred=False):
240
 
        self._look_busy()
241
 
        self.refresh_cache()
242
 
 
243
 
        if self.cache is None:
244
 
            return
245
 
 
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)
251
 
 
252
 
    def _check_oem_metapackages(self):
253
 
        di = distro_info.UbuntuDistroInfo()
254
 
        codename = get_dist()
255
 
        lts = di.is_lts(codename)
256
 
        if not lts:
257
 
            return None
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):
261
 
            return None
262
 
 
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())
266
 
 
267
 
        # Packages that are already installed
268
 
        for pkg in self.cache:
269
 
            if fnmatch.fnmatch(pkg.name, 'oem-*-meta') \
270
 
               and pkg.installed:
271
 
                self.oem_metapackages.add(pkg)
272
 
 
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))
279
 
 
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
285
 
            if need_reboot:
286
 
                return NeedRestartDialog(self)
287
 
            dist_upgrade = self._check_meta_release()
288
 
            if dist_upgrade:
289
 
                return dist_upgrade
290
 
            elif cancelled_update:
291
 
                return StoppedUpdatesDialog(self)
292
 
            elif self.hwe_replacement_packages:
293
 
                return HWEUpgradeDialog(self)
294
 
            else:
295
 
                return NoUpdatesDialog(self, error_occurred=error_occurred)
296
 
        else:
297
 
            header = None
298
 
            desc = None
299
 
            if 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 "
304
 
                         "a previous check.")
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)
309
 
 
310
 
    def start_error(self, update_and_retry, header, desc):
311
 
        if update_and_retry:
312
 
            self._start_pane(UpdateErrorDialog(self, header, desc))
313
 
        else:
314
 
            self._start_pane(ErrorDialog(self, header, desc))
315
 
 
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))
320
 
 
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)
326
 
 
327
 
    def _check_meta_release(self):
328
 
        if self.meta_release is None:
329
 
            return None
330
 
 
331
 
        if self.meta_release.downloading:
332
 
            # Block until we get an answer
333
 
            GLib.idle_add(self._meta_release_wait_idle)
334
 
            Gtk.main()
335
 
 
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:
339
 
            return None
340
 
 
341
 
        # Check for end-of-life
342
 
        if self.meta_release.no_longer_supported:
343
 
            return UnsupportedDialog(self, self.meta_release)
344
 
 
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,
352
 
                                             self.arch)
353
 
            return DistUpgradeDialog(self, self.meta_release)
354
 
 
355
 
        return None
356
 
 
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:
362
 
            Gtk.main_quit()
363
 
        else:
364
 
            self.meta_release.connect("done_downloading", Gtk.main_quit)
365
 
        return False
366
 
 
367
 
    def _check_hwe_support_status(self):
368
 
        di = distro_info.UbuntuDistroInfo()
369
 
        codename = get_dist()
370
 
        lts = di.is_lts(codename)
371
 
        if not lts:
372
 
            return None
373
 
        HWE = "/usr/bin/hwe-support-status"
374
 
        if not os.path.exists(HWE):
375
 
            return None
376
 
        cmd = [HWE, "--show-replacements"]
377
 
        self._parse_hwe_support_status(cmd)
378
 
 
379
 
    def _parse_hwe_support_status(self, cmd):
380
 
        try:
381
 
            subprocess.check_output(cmd)
382
 
            # for debugging
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)
393
 
                # for debugging
394
 
                # print(self.hwe_replacement_packages)
395
 
 
396
 
    # fixme: we should probably abstract away all the stuff from libapt
397
 
    def refresh_cache(self):
398
 
        try:
399
 
            if self.cache is None:
400
 
                self.cache = MyCache(None)
401
 
            else:
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)
416
 
            return
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)
425
 
            return
426
 
 
427
 
        # Let the Gtk event loop breath if it hasn't had a chance.
428
 
        def iterate():
429
 
            while Gtk.events_pending():
430
 
                Gtk.main_iteration()
431
 
        iterate()
432
 
 
433
 
        self._check_oem_metapackages()
434
 
 
435
 
        self._get_ua_security_status()
436
 
 
437
 
        for pkgname in self.oem_metapackages:
438
 
            try:
439
 
                if not self.cache[pkgname].is_installed:
440
 
                    self.cache[pkgname].mark_install()
441
 
            except SystemError:
442
 
                pass
443
 
 
444
 
        self.update_list = UpdateList(self)
445
 
        try:
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)
458
 
 
459
 
        if self.update_list.distUpgradeWouldDelete > 0:
460
 
            self._start_pane(PartialUpgradeDialog(self))
461
 
 
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
465
 
        # listening on dbus
466
 
        try:
467
 
            bus = dbus.SessionBus()
468
 
        except Exception:
469
 
            print("warning: could not initiate dbus")
470
 
            return
471
 
        try:
472
 
            proxy_obj = bus.get_object('org.freedesktop.UpdateManager',
473
 
                                       '/org/freedesktop/UpdateManagerObject')
474
 
            iface = dbus.Interface(proxy_obj,
475
 
                                   'org.freedesktop.UpdateManagerIFace')
476
 
            iface.bringToFront()
477
 
            #print("send bringToFront")
478
 
            sys.exit(0)
479
 
        except dbus.DBusException:
480
 
            #print("no listening object (%s) " % e)
481
 
            bus_name = dbus.service.BusName('org.freedesktop.UpdateManager',
482
 
                                            bus)
483
 
            self.dbusController = UpdateManagerDbusController(self, bus_name)
484
 
 
485
 
 
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)
491
 
        self.parent = parent
492
 
        self.alert_watcher = AlertWatcher()
493
 
        self.alert_watcher.connect("network-alert", self._on_network_alert)
494
 
        self.connected = False
495
 
 
496
 
    @dbus.service.method('org.freedesktop.UpdateManagerIFace')
497
 
    def bringToFront(self):
498
 
        self.parent.present()
499
 
        return True
500
 
 
501
 
    @dbus.service.method('org.freedesktop.UpdateManagerIFace')
502
 
    def upgrade(self):
503
 
        try:
504
 
            self.parent.start_install()
505
 
            return True
506
 
        except Exception:
507
 
            return False
508
 
 
509
 
    def _on_network_alert(self, watcher, state):
510
 
        if state in NetworkManagerHelper.NM_STATE_CONNECTED_LIST:
511
 
            self.connected = True
512
 
        else:
513
 
            self.connected = False