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

« back to all changes in this revision

Viewing changes to DistUpgrade/DistUpgradeViewGtk.py

merge from move-changelogs branch

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# DistUpgradeViewGtk.py 
 
2
#  
 
3
#  Copyright (c) 2004-2006 Canonical
 
4
#  
 
5
#  Author: Michael Vogt <michael.vogt@ubuntu.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
 
 
22
from __future__ import absolute_import, print_function
 
23
 
 
24
import pygtk
 
25
pygtk.require('2.0')
 
26
import glib
 
27
import gtk
 
28
import gtk.gdk
 
29
import vte
 
30
import gobject
 
31
import pango
 
32
import sys
 
33
import locale
 
34
import logging
 
35
import time
 
36
import subprocess
 
37
 
 
38
import apt
 
39
import apt_pkg
 
40
import os
 
41
 
 
42
from .DistUpgradeApport import run_apport, apport_crash
 
43
 
 
44
from .DistUpgradeView import DistUpgradeView, FuzzyTimeToStr, InstallProgress, AcquireProgress
 
45
from .SimpleGtkbuilderApp import SimpleGtkbuilderApp
 
46
 
 
47
import gettext
 
48
from .DistUpgradeGettext import gettext as _
 
49
 
 
50
gobject.threads_init()
 
51
 
 
52
def utf8(str):
 
53
  return unicode(str, 'latin1').encode('utf-8')
 
54
 
 
55
class GtkCdromProgressAdapter(apt.progress.base.CdromProgress):
 
56
    """ Report the cdrom add progress
 
57
        Subclass this class to implement cdrom add progress reporting
 
58
    """
 
59
    def __init__(self, parent):
 
60
        self.status = parent.label_status
 
61
        self.progress = parent.progressbar_cache
 
62
        self.parent = parent
 
63
    def update(self, text, step):
 
64
        """ update is called regularly so that the gui can be redrawn """
 
65
        if text:
 
66
          self.status.set_text(text)
 
67
        self.progress.set_fraction(step/float(self.totalSteps))
 
68
        while gtk.events_pending():
 
69
          gtk.main_iteration()
 
70
    def ask_cdrom_name(self):
 
71
        return (False, "")
 
72
    def change_cdrom(self):
 
73
        return False
 
74
 
 
75
class GtkOpProgress(apt.progress.base.OpProgress):
 
76
  def __init__(self, progressbar):
 
77
      self.progressbar = progressbar
 
78
      #self.progressbar.set_pulse_step(0.01)
 
79
      #self.progressbar.pulse()
 
80
      self.fraction = 0.0
 
81
 
 
82
  def update(self, percent):
 
83
      #if percent > 99:
 
84
      #    self.progressbar.set_fraction(1)
 
85
      #else:
 
86
      #    self.progressbar.pulse()
 
87
      new_fraction = percent/100.0
 
88
      if abs(self.fraction-new_fraction) > 0.1:
 
89
        self.fraction = new_fraction
 
90
        self.progressbar.set_fraction(self.fraction)
 
91
      while gtk.events_pending():
 
92
          gtk.main_iteration()
 
93
 
 
94
  def done(self):
 
95
      self.progressbar.set_text(" ")
 
96
 
 
97
 
 
98
class GtkAcquireProgressAdapter(AcquireProgress):
 
99
    # FIXME: we really should have some sort of "we are at step"
 
100
    # xy in the gui
 
101
    # FIXME2: we need to thing about mediaCheck here too
 
102
    def __init__(self, parent):
 
103
        super(GtkAcquireProgressAdapter, self).__init__()
 
104
        # if this is set to false the download will cancel
 
105
        self.status = parent.label_status
 
106
        self.progress = parent.progressbar_cache
 
107
        self.parent = parent
 
108
        self.canceled = False
 
109
        self.button_cancel = parent.button_fetch_cancel
 
110
        self.button_cancel.connect('clicked', self.cancelClicked)
 
111
    def cancelClicked(self, widget):
 
112
        logging.debug("cancelClicked")
 
113
        self.canceled = True
 
114
    def media_change(self, medium, drive):
 
115
        #print("mediaChange %s %s" % (medium, drive))
 
116
        msg = _("Please insert '%s' into the drive '%s'") % (medium,drive)
 
117
        dialog = gtk.MessageDialog(parent=self.parent.window_main,
 
118
                                   flags=gtk.DIALOG_MODAL,
 
119
                                   type=gtk.MESSAGE_QUESTION,
 
120
                                   buttons=gtk.BUTTONS_OK_CANCEL)
 
121
        dialog.set_markup(msg)
 
122
        res = dialog.run()
 
123
        dialog.set_title("")
 
124
        dialog.destroy()
 
125
        if res == gtk.RESPONSE_OK:
 
126
            return True
 
127
        return False
 
128
    def start(self):
 
129
        #logging.debug("start")
 
130
        super(GtkAcquireProgressAdapter, self).start()
 
131
        self.progress.set_fraction(0)
 
132
        self.status.show()
 
133
        self.button_cancel.show()
 
134
    def stop(self):
 
135
        #logging.debug("stop")
 
136
        self.progress.set_text(" ")
 
137
        self.status.set_text(_("Fetching is complete"))
 
138
        self.button_cancel.hide()
 
139
    def pulse(self, owner):
 
140
        super(GtkAcquireProgressAdapter, self).pulse(owner)
 
141
        # only update if there is a noticable change
 
142
        if abs(self.percent-self.progress.get_fraction()*100.0) > 0.1:
 
143
          self.progress.set_fraction(self.percent/100.0)
 
144
          currentItem = self.current_items + 1
 
145
          if currentItem > self.total_items:
 
146
            currentItem = self.total_items
 
147
          if self.current_cps > 0:
 
148
            self.status.set_text(_("Fetching file %li of %li at %sB/s") % (
 
149
                currentItem, self.total_items, 
 
150
                apt_pkg.size_to_str(self.current_cps)))
 
151
            self.progress.set_text(_("About %s remaining") % FuzzyTimeToStr(
 
152
                self.eta))
 
153
          else:
 
154
            self.status.set_text(_("Fetching file %li of %li") % (
 
155
                currentItem, self.total_items))
 
156
            self.progress.set_text("  ")
 
157
        while gtk.events_pending():
 
158
            gtk.main_iteration()
 
159
        return (not self.canceled)
 
160
 
 
161
class GtkInstallProgressAdapter(InstallProgress):
 
162
    # timeout with no status change when the terminal is expanded
 
163
    # automatically
 
164
    TIMEOUT_TERMINAL_ACTIVITY = 240
 
165
    
 
166
    def __init__(self,parent):
 
167
        InstallProgress.__init__(self)
 
168
        self._cache = None
 
169
        self.label_status = parent.label_status
 
170
        self.progress = parent.progressbar_cache
 
171
        self.expander = parent.expander_terminal
 
172
        self.term = parent._term
 
173
        self.term.connect("child-exited", self.child_exited)
 
174
        self.parent = parent
 
175
        # setup the child waiting
 
176
        # some options for dpkg to make it die less easily
 
177
        apt_pkg.config.set("DPkg::StopOnError","False")
 
178
 
 
179
    def start_update(self):
 
180
        InstallProgress.start_update(self)
 
181
        self.finished = False
 
182
        # FIXME: add support for the timeout
 
183
        # of the terminal (to display something useful then)
 
184
        # -> longer term, move this code into python-apt 
 
185
        self.label_status.set_text(_("Applying changes"))
 
186
        self.progress.set_fraction(0.0)
 
187
        self.progress.set_text(" ")
 
188
        self.expander.set_sensitive(True)
 
189
        self.term.show()
 
190
        # if no libgtk2-perl is installed show the terminal
 
191
        frontend= os.environ.get("DEBIAN_FRONTEND") or "gnome"
 
192
        if frontend == "gnome" and self._cache:
 
193
          if (not "libgtk2-perl" in self._cache or 
 
194
              not self._cache["libgtk2-perl"].is_installed):
 
195
            frontend = "dialog"
 
196
            self.expander.set_expanded(True)
 
197
        self.env = ["VTE_PTY_KEEP_FD=%s"% self.writefd,
 
198
                    "APT_LISTCHANGES_FRONTEND=none"]
 
199
        if not os.environ.has_key("DEBIAN_FRONTEND"):
 
200
          self.env.append("DEBIAN_FRONTEND=%s" % frontend)
 
201
        # do a bit of time-keeping
 
202
        self.start_time = 0.0
 
203
        self.time_ui = 0.0
 
204
        self.last_activity = 0.0
 
205
        
 
206
    def error(self, pkg, errormsg):
 
207
        InstallProgress.error(self, pkg, errormsg)
 
208
        logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
 
209
        # we do not report followup errors from earlier failures
 
210
        if gettext.dgettext('dpkg', "dependency problems - leaving unconfigured") in errormsg:
 
211
          return False
 
212
 
 
213
        #self.expander_terminal.set_expanded(True)
 
214
        self.parent.dialog_error.set_transient_for(self.parent.window_main)
 
215
        summary = _("Could not install '%s'") % pkg
 
216
        msg = _("The upgrade will continue but the '%s' package may not "
 
217
                "be in a working state. Please consider submitting a "
 
218
                "bug report about it.") % pkg
 
219
        markup="<big><b>%s</b></big>\n\n%s" % (summary, msg)
 
220
        self.parent.dialog_error.realize()
 
221
        self.parent.dialog_error.set_title("")
 
222
        self.parent.dialog_error.window.set_functions(gtk.gdk.FUNC_MOVE)
 
223
        self.parent.label_error.set_markup(markup)
 
224
        self.parent.textview_error.get_buffer().set_text(utf8(errormsg))
 
225
        self.parent.scroll_error.show()
 
226
        self.parent.dialog_error.run()
 
227
        self.parent.dialog_error.hide()
 
228
 
 
229
    def conffile(self, current, new):
 
230
        logging.debug("got a conffile-prompt from dpkg for file: '%s'" % current)
 
231
        start = time.time()
 
232
        #self.expander.set_expanded(True)
 
233
        prim = _("Replace the customized configuration file\n'%s'?") % current
 
234
        sec = _("You will lose any changes you have made to this "
 
235
                "configuration file if you choose to replace it with "
 
236
                "a newer version.")
 
237
        markup = "<span weight=\"bold\" size=\"larger\">%s </span> \n\n%s" % (prim, sec)
 
238
        self.parent.label_conffile.set_markup(markup)
 
239
        self.parent.dialog_conffile.set_title("")
 
240
        self.parent.dialog_conffile.set_transient_for(self.parent.window_main)
 
241
 
 
242
        # workaround silly dpkg 
 
243
        if not os.path.exists(current):
 
244
          current = current+".dpkg-dist"
 
245
 
 
246
        # now get the diff
 
247
        if os.path.exists("/usr/bin/diff"):
 
248
          cmd = ["/usr/bin/diff", "-u", current, new]
 
249
          diff = utf8(subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0])
 
250
          self.parent.textview_conffile.get_buffer().set_text(diff)
 
251
        else:
 
252
          self.parent.textview_conffile.get_buffer().set_text(_("The 'diff' command was not found"))
 
253
        res = self.parent.dialog_conffile.run()
 
254
        self.parent.dialog_conffile.hide()
 
255
        self.time_ui += time.time() - start
 
256
        # if replace, send this to the terminal
 
257
        if res == gtk.RESPONSE_YES:
 
258
          self.term.feed_child("y\n", -1)
 
259
        else:
 
260
          self.term.feed_child("n\n", -1)
 
261
        
 
262
    def fork(self):
 
263
        pid = self.term.forkpty(envv=self.env)
 
264
        if pid == 0:
 
265
          # WORKAROUND for broken feisty vte where envv does not work)
 
266
          for env in self.env:
 
267
            (key, value) = env.split("=")
 
268
            os.environ[key] = value
 
269
            # force dpkg terminal messages untranslated for better bug
 
270
            # duplication detection
 
271
            os.environ["DPKG_UNTRANSLATED_MESSAGES"] = "1"
 
272
          # HACK to work around bug in python/vte and unregister the logging
 
273
          #      atexit func in the child
 
274
          sys.exitfunc = lambda: True
 
275
        return pid
 
276
 
 
277
    def status_change(self, pkg, percent, status):
 
278
        # start the timer when the first package changes its status
 
279
        if self.start_time == 0.0:
 
280
          #print("setting start time to %s" % self.start_time)
 
281
          self.start_time = time.time()
 
282
        # only update if there is a noticable change
 
283
        if abs(percent-self.progress.get_fraction()*100.0) > 0.1:
 
284
          self.progress.set_fraction(float(percent)/100.0)
 
285
          self.label_status.set_text(status.strip())
 
286
        # start showing when we gathered some data
 
287
        if percent > 1.0:
 
288
          self.last_activity = time.time()
 
289
          self.activity_timeout_reported = False
 
290
          delta = self.last_activity - self.start_time
 
291
          # time wasted in conffile questions (or other ui activity)
 
292
          delta -= self.time_ui
 
293
          time_per_percent = (float(delta)/percent)
 
294
          eta = (100.0 - percent) * time_per_percent
 
295
          # only show if we have some sensible data (60sec < eta < 2days)
 
296
          if eta > 61.0 and eta < (60*60*24*2):
 
297
            self.progress.set_text(_("About %s remaining") % FuzzyTimeToStr(eta))
 
298
          else:
 
299
            self.progress.set_text(" ")
 
300
          # 2 == WEBKIT_LOAD_FINISHED - the enums is not exposed via python
 
301
          if (self.parent._webkit_view and
 
302
              self.parent._webkit_view.get_property("load-status") == 2):
 
303
            self.parent._webkit_view.execute_script('progress("%s")' % percent)
 
304
 
 
305
    def child_exited(self, term):
 
306
        # we need to capture the full status here (not only the WEXITSTATUS)
 
307
        self.apt_status = term.get_child_exit_status()
 
308
        self.finished = True
 
309
 
 
310
    def wait_child(self):
 
311
        while not self.finished:
 
312
            self.update_interface()
 
313
        return self.apt_status
 
314
 
 
315
    def finish_update(self):
 
316
        self.label_status.set_text("")
 
317
    
 
318
    def update_interface(self):
 
319
        InstallProgress.update_interface(self)
 
320
        # check if we haven't started yet with packages, pulse then
 
321
        if self.start_time == 0.0:
 
322
          self.progress.pulse()
 
323
          time.sleep(0.2)
 
324
        # check about terminal activity
 
325
        if self.last_activity > 0 and \
 
326
           (self.last_activity + self.TIMEOUT_TERMINAL_ACTIVITY) < time.time():
 
327
          if not self.activity_timeout_reported:
 
328
            logging.warning("no activity on terminal for %s seconds (%s)" % (self.TIMEOUT_TERMINAL_ACTIVITY, self.label_status.get_text()))
 
329
            self.activity_timeout_reported = True
 
330
          self.parent.expander_terminal.set_expanded(True)
 
331
        # process events
 
332
        while gtk.events_pending():
 
333
            gtk.main_iteration()
 
334
        time.sleep(0.01)
 
335
 
 
336
class DistUpgradeVteTerminal(object):
 
337
  def __init__(self, parent, term):
 
338
    self.term = term
 
339
    self.parent = parent
 
340
  def call(self, cmd, hidden=False):
 
341
    def wait_for_child(widget):
 
342
      #print("wait for child finished")
 
343
      self.finished=True
 
344
    self.term.show()
 
345
    self.term.connect("child-exited", wait_for_child)
 
346
    self.parent.expander_terminal.set_sensitive(True)
 
347
    if hidden==False:
 
348
      self.parent.expander_terminal.set_expanded(True)
 
349
    self.finished = False
 
350
    pid = self.term.fork_command(command=cmd[0],argv=cmd)
 
351
    if pid < 0:
 
352
      # error
 
353
      return 
 
354
    while not self.finished:
 
355
      while gtk.events_pending():
 
356
        gtk.main_iteration()
 
357
      time.sleep(0.1)
 
358
    del self.finished
 
359
 
 
360
class HtmlView(object):
 
361
  def __init__(self, webkit_view):
 
362
    self._webkit_view = webkit_view
 
363
  def open(self, url):
 
364
    if not self._webkit_view:
 
365
      return
 
366
    self._webkit_view.open(url)
 
367
    self._webkit_view.connect("load-finished", self._on_load_finished)
 
368
  def show(self):
 
369
    self._webkit_view.show()
 
370
  def hide(self):
 
371
    self._webkit_view.hide()
 
372
  def _on_load_finished(self, view, frame):
 
373
    view.show()
 
374
 
 
375
class DistUpgradeViewGtk(DistUpgradeView,SimpleGtkbuilderApp):
 
376
    " gtk frontend of the distUpgrade tool "
 
377
    def __init__(self, datadir=None, logdir=None):
 
378
        DistUpgradeView.__init__(self)
 
379
        self.logdir = logdir
 
380
        if not datadir:
 
381
          localedir=os.path.join(os.getcwd(),"mo")
 
382
          gladedir=os.getcwd()
 
383
        else:
 
384
          localedir="/usr/share/locale/"
 
385
          gladedir=os.path.join(datadir, "gtkbuilder")
 
386
 
 
387
        # check if we have a display etc
 
388
        gtk.init_check()
 
389
 
 
390
        try:
 
391
          locale.bindtextdomain("update-manager",localedir)
 
392
          gettext.textdomain("update-manager")
 
393
        except Exception as e:
 
394
          logging.warning("Error setting locales (%s)" % e)
 
395
        
 
396
        icons = gtk.icon_theme_get_default()
 
397
        try:
 
398
          gtk.window_set_default_icon(icons.load_icon("system-software-update", 32, 0))
 
399
        except gobject.GError as e:
 
400
          logging.debug("error setting default icon, ignoring (%s)" % e)
 
401
          pass
 
402
        SimpleGtkbuilderApp.__init__(self, 
 
403
                                     gladedir+"/DistUpgrade.ui", 
 
404
                                     "update-manager")
 
405
        # terminal stuff
 
406
        self.create_terminal()
 
407
 
 
408
        self.prev_step = 0 # keep a record of the latest step
 
409
        # we don't use this currently
 
410
        #self.window_main.set_keep_above(True)
 
411
        self.icontheme = gtk.icon_theme_get_default()
 
412
        # we keep a reference pngloader around so that its in memory
 
413
        # -> this avoid the issue that during the dapper->edgy upgrade
 
414
        #    the loaders move from /usr/lib/gtk/2.4.0/loaders to 2.10.0
 
415
        self.pngloader = gtk.gdk.PixbufLoader("png")
 
416
        try:
 
417
          self.svgloader = gtk.gdk.PixbufLoader("svg")
 
418
          self.svgloader.close()
 
419
        except gobject.GError as e:
 
420
          logging.debug("svg pixbuf loader failed (%s)" % e)
 
421
          pass
 
422
        try:
 
423
          import webkit
 
424
          self._webkit_view = webkit.WebView()
 
425
          self.vbox_main.pack_end(self._webkit_view)
 
426
        except:
 
427
          logging.exception("html widget")
 
428
          self._webkit_view = None
 
429
        self.window_main.realize()
 
430
        self.window_main.window.set_functions(gtk.gdk.FUNC_MOVE)
 
431
        self._opCacheProgress = GtkOpProgress(self.progressbar_cache)
 
432
        self._acquireProgress = GtkAcquireProgressAdapter(self)
 
433
        self._cdromProgress = GtkCdromProgressAdapter(self)
 
434
        self._installProgress = GtkInstallProgressAdapter(self)
 
435
        # details dialog
 
436
        self.details_list = gtk.TreeStore(gobject.TYPE_STRING)
 
437
        column = gtk.TreeViewColumn("")
 
438
        render = gtk.CellRendererText()
 
439
        column.pack_start(render, True)
 
440
        column.add_attribute(render, "markup", 0)
 
441
        self.treeview_details.append_column(column)
 
442
        self.details_list.set_sort_column_id(0, gtk.SORT_ASCENDING)
 
443
        self.treeview_details.set_model(self.details_list)
 
444
        # Use italic style in the status labels
 
445
        attrlist=pango.AttrList()
 
446
        #attr = pango.AttrStyle(pango.STYLE_ITALIC, 0, -1)
 
447
        attr = pango.AttrScale(pango.SCALE_SMALL, 0, -1)
 
448
        attrlist.insert(attr)
 
449
        self.label_status.set_property("attributes", attrlist)
 
450
        # reasonable fault handler
 
451
        sys.excepthook = self._handleException
 
452
 
 
453
    def _handleException(self, type, value, tb):
 
454
      # we handle the exception here, hand it to apport and run the
 
455
      # apport gui manually after it because we kill u-m during the upgrade
 
456
      # to prevent it from poping up for reboot notifications or FF restart
 
457
      # notifications or somesuch
 
458
      import traceback
 
459
      lines = traceback.format_exception(type, value, tb)
 
460
      logging.error("not handled expection:\n%s" % "\n".join(lines))
 
461
      # we can't be sure that apport will run in the middle of a upgrade
 
462
      # so we still show a error message here
 
463
      apport_crash(type, value, tb)
 
464
      if not run_apport():
 
465
        self.error(_("A fatal error occurred"),
 
466
                   _("Please report this as a bug (if you haven't already) and include the "
 
467
                     "files /var/log/dist-upgrade/main.log and "
 
468
                     "/var/log/dist-upgrade/apt.log "
 
469
                     "in your report. The upgrade has aborted.\n"
 
470
                     "Your original sources.list was saved in "
 
471
                     "/etc/apt/sources.list.distUpgrade."),
 
472
                   "\n".join(lines))
 
473
      sys.exit(1)
 
474
 
 
475
    def getTerminal(self):
 
476
        return DistUpgradeVteTerminal(self, self._term)
 
477
    def getHtmlView(self):
 
478
        return HtmlView(self._webkit_view)
 
479
 
 
480
    def _key_press_handler(self, widget, keyev):
 
481
      # user pressed ctrl-c
 
482
      if len(keyev.string) == 1 and ord(keyev.string) == 3:
 
483
        summary = _("Ctrl-c pressed")
 
484
        msg = _("This will abort the operation and may leave the system "
 
485
                "in a broken state. Are you sure you want to do that?")
 
486
        res = self.askYesNoQuestion(summary, msg)
 
487
        logging.warning("ctrl-c press detected, user decided to pass it "
 
488
                        "on: %s", res)        
 
489
        return not res
 
490
      return False
 
491
 
 
492
    def create_terminal(self):
 
493
        " helper to create a vte terminal "
 
494
        self._term = vte.Terminal()
 
495
        self._term.connect("key-press-event", self._key_press_handler)
 
496
        self._term.set_font_from_string("monospace 10")
 
497
        self._term.connect("contents-changed", self._term_content_changed)
 
498
        self._terminal_lines = []
 
499
        self.hbox_custom.pack_start(self._term)
 
500
        self._term.realize()
 
501
        self.vscrollbar_terminal = gtk.VScrollbar()
 
502
        self.vscrollbar_terminal.show()
 
503
        self.hbox_custom.pack_start(self.vscrollbar_terminal)
 
504
        self.vscrollbar_terminal.set_adjustment(self._term.get_adjustment())
 
505
        try:
 
506
          self._terminal_log = open(os.path.join(self.logdir,"term.log"),"w")
 
507
        except Exception:
 
508
          # if something goes wrong (permission denied etc), use stdout
 
509
          self._terminal_log = sys.stdout
 
510
        return self._term
 
511
 
 
512
    def _term_content_changed(self, term):
 
513
        " called when the *visible* part of the terminal changes "
 
514
        # get the current visible text, 
 
515
        current_text = self._term.get_text(lambda a,b,c,d: True)
 
516
        # see what we have currently and only print stuff that wasn't
 
517
        # visible last time
 
518
        new_lines = []
 
519
        for line in current_text.split("\n"):
 
520
          new_lines.append(line)
 
521
          if not line in self._terminal_lines:
 
522
            self._terminal_log.write(line+"\n")
 
523
            try:
 
524
              self._terminal_log.flush()
 
525
            except IOError:
 
526
              logging.exception("flush()")
 
527
        self._terminal_lines = new_lines
 
528
    def getAcquireProgress(self):
 
529
        return self._acquireProgress
 
530
    def getInstallProgress(self, cache):
 
531
        self._installProgress._cache = cache
 
532
        return self._installProgress
 
533
    def getOpCacheProgress(self):
 
534
        return self._opCacheProgress
 
535
    def getCdromProgress(self):
 
536
        return self._cdromProgress
 
537
    def updateStatus(self, msg):
 
538
        self.label_status.set_text("%s" % msg)
 
539
    def hideStep(self, step):
 
540
        image = getattr(self,"image_step%i" % step)
 
541
        label = getattr(self,"label_step%i" % step)
 
542
        #arrow = getattr(self,"arrow_step%i" % step)
 
543
        image.hide()
 
544
        label.hide()
 
545
    def showStep(self, step):
 
546
        image = getattr(self,"image_step%i" % step)
 
547
        label = getattr(self,"label_step%i" % step)
 
548
        image.show()
 
549
        label.show()
 
550
    def abort(self):
 
551
        size = gtk.ICON_SIZE_MENU
 
552
        step = self.prev_step
 
553
        if step > 0:
 
554
            image = getattr(self,"image_step%i" % step)
 
555
            arrow = getattr(self,"arrow_step%i" % step)
 
556
            image.set_from_stock(gtk.STOCK_CANCEL, size)
 
557
            image.show()
 
558
            arrow.hide()
 
559
    def setStep(self, step):
 
560
        if self.icontheme.rescan_if_needed():
 
561
          logging.debug("icon theme changed, re-reading")
 
562
        # first update the "previous" step as completed
 
563
        size = gtk.ICON_SIZE_MENU
 
564
        attrlist=pango.AttrList()
 
565
        if self.prev_step:
 
566
            image = getattr(self,"image_step%i" % self.prev_step)
 
567
            label = getattr(self,"label_step%i" % self.prev_step)
 
568
            arrow = getattr(self,"arrow_step%i" % self.prev_step)
 
569
            label.set_property("attributes",attrlist)
 
570
            image.set_from_stock(gtk.STOCK_APPLY, size)
 
571
            image.show()
 
572
            arrow.hide()
 
573
        self.prev_step = step
 
574
        # show the an arrow for the current step and make the label bold
 
575
        image = getattr(self,"image_step%i" % step)
 
576
        label = getattr(self,"label_step%i" % step)
 
577
        arrow = getattr(self,"arrow_step%i" % step)
 
578
        # check if that step was not hidden with hideStep()
 
579
        if not label.get_property("visible"):
 
580
          return
 
581
        arrow.show()
 
582
        image.hide()
 
583
        attr = pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1)
 
584
        attrlist.insert(attr)
 
585
        label.set_property("attributes",attrlist)
 
586
 
 
587
    def information(self, summary, msg, extended_msg=None):
 
588
      self.dialog_information.set_title("")
 
589
      self.dialog_information.set_transient_for(self.window_main)
 
590
      msg = "<big><b>%s</b></big>\n\n%s" % (summary,msg)
 
591
      self.label_information.set_markup(msg)
 
592
      if extended_msg != None:
 
593
        buffer = self.textview_information.get_buffer()
 
594
        buffer.set_text(extended_msg)
 
595
        self.scroll_information.show()
 
596
      else:
 
597
        self.scroll_information.hide()
 
598
      self.dialog_information.realize()
 
599
      self.dialog_information.window.set_functions(gtk.gdk.FUNC_MOVE)
 
600
      self.dialog_information.run()
 
601
      self.dialog_information.hide()
 
602
      while gtk.events_pending():
 
603
        gtk.main_iteration()
 
604
 
 
605
    def error(self, summary, msg, extended_msg=None):
 
606
        self.dialog_error.set_title("")
 
607
        self.dialog_error.set_transient_for(self.window_main)
 
608
        #self.expander_terminal.set_expanded(True)
 
609
        msg="<big><b>%s</b></big>\n\n%s" % (summary, msg)
 
610
        self.label_error.set_markup(msg)
 
611
        if extended_msg != None:
 
612
            buffer = self.textview_error.get_buffer()
 
613
            buffer.set_text(extended_msg)
 
614
            self.scroll_error.show()
 
615
        else:
 
616
            self.scroll_error.hide()
 
617
        self.dialog_error.realize()
 
618
        self.dialog_error.window.set_functions(gtk.gdk.FUNC_MOVE)
 
619
        self.dialog_error.run()
 
620
        self.dialog_error.hide()
 
621
        return False
 
622
 
 
623
    def confirmChanges(self, summary, changes, demotions, downloadSize, 
 
624
                       actions=None, removal_bold=True):
 
625
        # FIXME: add a whitelist here for packages that we expect to be
 
626
        # removed (how to calc this automatically?)
 
627
        if not DistUpgradeView.confirmChanges(self, summary, changes, 
 
628
                                              demotions, downloadSize):
 
629
          return False
 
630
        # append warning
 
631
        self.confirmChangesMessage +=  "\n\n<b>%s</b>" %  \
 
632
            _("To prevent data loss close all open "
 
633
              "applications and documents.")
 
634
 
 
635
        if actions != None:
 
636
            self.button_cancel_changes.set_use_stock(False)
 
637
            self.button_cancel_changes.set_use_underline(True)
 
638
            self.button_cancel_changes.set_label(actions[0])
 
639
            self.button_confirm_changes.set_label(actions[1])
 
640
 
 
641
        self.label_summary.set_markup("<big><b>%s</b></big>" % summary)
 
642
        self.label_changes.set_markup(self.confirmChangesMessage)
 
643
        # fill in the details
 
644
        self.details_list.clear()
 
645
        for (parent_text, details_list) in ( 
 
646
            ( _("No longer supported by Canonical (%s)"), self.demotions),
 
647
            ( _("<b>Downgrade (%s)</b>"), self.toDowngrade),
 
648
            ( _("Remove (%s)"), self.toRemove),
 
649
            ( _("No longer needed (%s)"), self.toRemoveAuto),
 
650
            ( _("Install (%s)"), self.toInstall),
 
651
            ( _("Upgrade (%s)"), self.toUpgrade),
 
652
          ):
 
653
          if details_list:
 
654
            node = self.details_list.append(None, 
 
655
                                            [parent_text % len(details_list)])
 
656
            for pkg in details_list:
 
657
              self.details_list.append(node, ["<b>%s</b> - %s" % (
 
658
                    pkg.name, glib.markup_escape_text(pkg.summary))])
 
659
        # prepare dialog
 
660
        self.dialog_changes.realize()
 
661
        self.dialog_changes.set_transient_for(self.window_main)
 
662
        self.dialog_changes.set_title("")
 
663
        self.dialog_changes.window.set_functions(gtk.gdk.FUNC_MOVE|
 
664
                                                 gtk.gdk.FUNC_RESIZE)
 
665
        res = self.dialog_changes.run()
 
666
        self.dialog_changes.hide()
 
667
        if res == gtk.RESPONSE_YES:
 
668
            return True
 
669
        return False
 
670
 
 
671
    def askYesNoQuestion(self, summary, msg, default='No'):
 
672
        msg = "<big><b>%s</b></big>\n\n%s" % (summary,msg)
 
673
        dialog = gtk.MessageDialog(parent=self.window_main,
 
674
                                   flags=gtk.DIALOG_MODAL,
 
675
                                   type=gtk.MESSAGE_QUESTION,
 
676
                                   buttons=gtk.BUTTONS_YES_NO)
 
677
        dialog.set_title("")
 
678
        if default == 'No':
 
679
          dialog.set_default_response(gtk.RESPONSE_NO)
 
680
        else:
 
681
          dialog.set_default_response(gtk.RESPONSE_YES)
 
682
        dialog.set_markup(msg)
 
683
        res = dialog.run()
 
684
        dialog.destroy()
 
685
        if res == gtk.RESPONSE_YES:
 
686
            return True
 
687
        return False
 
688
    
 
689
    def confirmRestart(self):
 
690
        self.dialog_restart.set_transient_for(self.window_main)
 
691
        self.dialog_restart.set_title("")
 
692
        self.dialog_restart.realize()
 
693
        self.dialog_restart.window.set_functions(gtk.gdk.FUNC_MOVE)
 
694
        res = self.dialog_restart.run()
 
695
        self.dialog_restart.hide()
 
696
        if res == gtk.RESPONSE_YES:
 
697
            return True
 
698
        return False
 
699
 
 
700
    def processEvents(self):
 
701
        while gtk.events_pending():
 
702
            gtk.main_iteration()
 
703
 
 
704
    def pulseProgress(self, finished=False):
 
705
      self.progressbar_cache.pulse()
 
706
      if finished:
 
707
        self.progressbar_cache.set_fraction(1.0)
 
708
 
 
709
    def on_window_main_delete_event(self, widget, event):
 
710
        self.dialog_cancel.set_transient_for(self.window_main)
 
711
        self.dialog_cancel.set_title("")
 
712
        self.dialog_cancel.realize()
 
713
        self.dialog_cancel.window.set_functions(gtk.gdk.FUNC_MOVE)
 
714
        res = self.dialog_cancel.run()
 
715
        self.dialog_cancel.hide()
 
716
        if res == gtk.RESPONSE_CANCEL:
 
717
            sys.exit(1)
 
718
        return True
 
719
 
 
720
if __name__ == "__main__":
 
721
  
 
722
  view = DistUpgradeViewGtk()
 
723
  fp = GtkAcquireProgressAdapter(view)
 
724
  ip = GtkInstallProgressAdapter(view)
 
725
 
 
726
  cache = apt.Cache()
 
727
  for pkg in sys.argv[1:]:
 
728
    if cache[pkg].is_installed:
 
729
      cache[pkg].mark_delete()
 
730
    else:
 
731
      cache[pkg].mark_install()
 
732
  cache.commit(fp,ip)
 
733
  gtk.main()
 
734
  sys.exit(0)
 
735
  
 
736
  #sys.exit(0)
 
737
  ip.conffile("TODO","TODO~")
 
738
  view.getTerminal().call(["dpkg","--configure","-a"])
 
739
  #view.getTerminal().call(["ls","-R","/usr"])
 
740
  view.error("short","long",
 
741
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
742
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
743
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
744
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
745
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
746
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
747
             "asfds afsdj af asdf asdf asf dsa fadsf asdf as fasf sextended\n"
 
748
             )
 
749
  view.confirmChanges("xx",[], 100)
 
750