~ubuntu-core-dev/ubuntu-release-upgrader/trunk

« back to all changes in this revision

Viewing changes to src/update-manager.in

  • Committer: Michael Vogt
  • Date: 2005-11-15 13:18:07 UTC
  • Revision ID: egon@top-20051115131807-12fada324eb74180
* initial revision (after accidently killing it)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python2.4
 
2
# update-manager.in - easy updating application
 
3
#  
 
4
#  Copyright (c) 2004 Canonical
 
5
#                2004 Michiel Sikkes
 
6
#  
 
7
#  Author: Michiel Sikkes <michiel@eyesopened.nl>
 
8
#          Michael Vogt <mvo@debian.org>
 
9
 
10
#  This program is free software; you can redistribute it and/or 
 
11
#  modify it under the terms of the GNU General Public License as 
 
12
#  published by the Free Software Foundation; either version 2 of the
 
13
#  License, or (at your option) any later version.
 
14
 
15
#  This program is distributed in the hope that it will be useful,
 
16
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
#  GNU General Public License for more details.
 
19
 
20
#  You should have received a copy of the GNU General Public License
 
21
#  along with this program; if not, write to the Free Software
 
22
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 
23
#  USA
 
24
 
 
25
import pygtk
 
26
pygtk.require('2.0')
 
27
import gtk
 
28
import gtk.gdk
 
29
import gtk.glade
 
30
import gobject
 
31
import gnome
 
32
import apt_pkg
 
33
import gettext
 
34
import copy
 
35
import string
 
36
import sys
 
37
import os
 
38
import os.path
 
39
import urllib2
 
40
import re
 
41
import thread
 
42
import tempfile
 
43
import time
 
44
import rfc822
 
45
import gconf
 
46
import pango
 
47
import subprocess
 
48
import pwd
 
49
import xml.sax.saxutils
 
50
 
 
51
 
 
52
# FIXME:
 
53
# - cary a reference to the update-class around in the ListStore
 
54
# - kill "all_changes" and move the changes into the "Update" class
 
55
 
 
56
# list constants
 
57
(LIST_INSTALL, LIST_CONTENTS, LIST_NAME, LIST_SHORTDESC,
 
58
 LIST_VERSION, LIST_LONG_DESCR, LIST_PKG) = range(7)
 
59
 
 
60
# actions for "invoke_manager"
 
61
(INSTALL, UPDATE) = range(2)
 
62
 
 
63
SYNAPTIC_PINFILE = "/var/lib/synaptic/preferences"
 
64
 
 
65
METARELEASE_URI = "http://changelogs.ubuntu.com/meta-release"
 
66
#METARELEASE_URI = "http://people.ubuntu.com/~mvo/meta-release-test"
 
67
METARELEASE_FILE = "/var/lib/update-manager/meta-release"
 
68
 
 
69
CHANGELOGS_URI="http://changelogs.ubuntu.com/changelogs/pool/%s/%s/%s/%s_%s/changelog"
 
70
 
 
71
# fixme: use a utils package for this sort of stuff
 
72
def str_to_bool(str):
 
73
  if str == "0" or str.upper() == "FALSE":
 
74
    return False
 
75
  return True
 
76
 
 
77
def utf8(str):
 
78
  return unicode(str, 'latin1').encode('utf-8')
 
79
 
 
80
class Update:
 
81
 
 
82
  def __init__(self, package, cache, records, depcache):
 
83
    #package = cache[name]
 
84
    name = package.Name
 
85
    version = depcache.GetCandidateVer(package)
 
86
    file, index = version.FileList.pop(0)
 
87
    records.Lookup((file, index))
 
88
 
 
89
    self.name = name
 
90
    self.version = version.VerStr
 
91
    self.shortdesc = records.ShortDesc
 
92
    self.longdesc = ""
 
93
    self.size = version.Size
 
94
 
 
95
    longdesc = records.LongDesc
 
96
    lines = longdesc.split("\n")
 
97
    lines.pop(0)
 
98
    for line in lines:
 
99
      line = line[1:]
 
100
      first_char = string.strip(line)[0]
 
101
      if line == ".":
 
102
        self.longdesc = self.longdesc + "\n"
 
103
      else:
 
104
        self.longdesc = self.longdesc + line + "\n"
 
105
 
 
106
class UpdateList:
 
107
  def __init__(self, parent_window):
 
108
    self.pkgs = []
 
109
    self.num_updates = 0
 
110
    self.parent_window = parent_window
 
111
 
 
112
  def saveDistUpgrade(self, cache, depcache):
 
113
    """ this functions mimics a upgrade but will never remove anything """
 
114
    depcache.Upgrade(True)
 
115
    if depcache.DelCount > 0:
 
116
      # nice try, falling back
 
117
      for pkg in cache.Packages:
 
118
        depcache.MarkKeep(pkg)
 
119
      assert depcache.BrokenCount == 0 and depcache.DelCount == 0
 
120
      depcache.Upgrade()
 
121
 
 
122
  def update(self, cache, records, depcache):
 
123
    held_back = []
 
124
    broken = []
 
125
    self.saveDistUpgrade(cache, depcache)
 
126
    for pkg in cache.Packages:
 
127
      if depcache.MarkedUpgrade(pkg) or depcache.MarkedInstall(pkg):
 
128
        self.pkgs.append(Update(pkg, cache, records, depcache))
 
129
        self.num_updates = self.num_updates + 1
 
130
      elif depcache.IsInstBroken(pkg) or depcache.IsNowBroken(pkg):
 
131
          broken.append(pkg.Name)
 
132
      elif pkg.CurrentVer != None and depcache.IsUpgradable(pkg):
 
133
        #print "MarkedKeep: %s " % pkg.Name
 
134
          held_back.append(pkg.Name)
 
135
    self.pkgs.sort(lambda x,y: cmp(x.name,y.name))
 
136
    if depcache.BrokenCount > 0:
 
137
      # FIXME: show what packages are broken
 
138
      msg=("<big><b>%s</b></big>\n\n%s"%(_("Your system has broken packages!"),
 
139
                                         _("This means that some dependencies "
 
140
                                           "of the installed packages are not "
 
141
                                          "satisfied. Please use \"Synaptic\" "
 
142
                                           "or \"apt-get\" to fix the "
 
143
                                           "situation."
 
144
                                           )))
 
145
      dialog = gtk.MessageDialog(self.parent_window, 0, gtk.MESSAGE_ERROR,
 
146
                                 gtk.BUTTONS_OK,"")
 
147
      dialog.set_markup(msg)
 
148
      dialog.vbox.set_spacing(6)
 
149
      dialog.run()
 
150
      dialog.destroy()
 
151
      sys.exit(1)
 
152
    if depcache.KeepCount > 0:
 
153
      #print "WARNING, keeping packages"
 
154
      msg=("<big><b>%s</b></big>\n\n%s"%(_("It is not possible to upgrade "
 
155
                                           "all packages."),
 
156
                                         _("This means that "
 
157
                                           "besides the actual upgrade of the "
 
158
                                           "packages some further action "
 
159
                                           "(such as installing or removing "
 
160
                                           "packages) "
 
161
                                           "is required. Please use Synaptic "
 
162
                                           "\"Smart Upgrade\" or "
 
163
                                           "\"apt-get dist-upgrade\" to fix "
 
164
                                           "the situation."
 
165
                                           )))
 
166
      dialog = gtk.MessageDialog(self.parent_window, 0, gtk.MESSAGE_INFO,
 
167
                                 gtk.BUTTONS_OK,"")
 
168
      dialog.set_default_response(gtk.RESPONSE_OK)
 
169
      dialog.set_markup(msg)
 
170
      dialog.vbox.set_spacing(6)
 
171
      label = gtk.Label(_("The following packages are not upgraded: "))
 
172
      label.set_alignment(0.0,0.5)
 
173
      dialog.set_border_width(6)
 
174
      label.show()
 
175
      dialog.vbox.pack_start(label)
 
176
      scroll = gtk.ScrolledWindow()
 
177
      scroll.set_size_request(-1,200)
 
178
      scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
 
179
      text = gtk.TextView()
 
180
      text.set_editable(False)
 
181
      text.set_cursor_visible(False)
 
182
      buf = text.get_buffer()
 
183
      held_back.sort()
 
184
      buf.set_text("\n".join(held_back))
 
185
      scroll.add(text)
 
186
      dialog.vbox.pack_start(scroll)
 
187
      scroll.show_all()
 
188
      dialog.run()
 
189
      dialog.destroy()
 
190
 
 
191
        
 
192
class UpdateManager:
 
193
 
 
194
  # FIXME: wrong location for this func
 
195
  # don't touch the gui in this function, it needs to be thread-safe
 
196
  def get_changelog(self, name, lock):
 
197
    pkg = self.cache[name]
 
198
 
 
199
    # FIXME: not correct, need to get canidateVer
 
200
    version = self.depcache.GetCandidateVer(pkg)
 
201
    file, index = version.FileList.pop(0)
 
202
    self.records.Lookup((file, index))
 
203
    if self.records.SourcePkg != "":
 
204
      srcpkg = self.records.SourcePkg
 
205
    else:
 
206
      srcpkg = name
 
207
 
 
208
    src_section = "main"
 
209
    l = string.split(pkg.Section,"/")
 
210
    if len(l) > 1:
 
211
      sec_section = l[0]
 
212
    
 
213
    prefix = srcpkg[0]
 
214
    if srcpkg.startswith("lib"):
 
215
      prefix = "lib" + srcpkg[3]
 
216
      
 
217
    verstr = version.VerStr
 
218
    l = string.split(verstr,":")
 
219
    if len(l) > 1:
 
220
      verstr = l[1]
 
221
 
 
222
    try:
 
223
      uri = CHANGELOGS_URI % (src_section,prefix,srcpkg,srcpkg, verstr)
 
224
      changelog = urllib2.urlopen(uri)
 
225
      #print changelog.read()
 
226
      # do only get the lines that are new
 
227
      alllines = ""
 
228
      regexp = "^%s \((.*)\)(.*)$" % (srcpkg)
 
229
 
 
230
      i=0
 
231
      while True:
 
232
        line = changelog.readline()
 
233
        #print line
 
234
        if line == "":
 
235
          break
 
236
        match = re.match(regexp,line)
 
237
        if match:
 
238
          if apt_pkg.VersionCompare(match.group(1),pkg.CurrentVer.VerStr) <= 0:
 
239
            break
 
240
          # EOF (shouldn't really happen)
 
241
        alllines = alllines + line
 
242
 
 
243
      # only write if we where not canceld
 
244
      if lock.locked():
 
245
        self.all_changes[name] = [alllines, srcpkg]
 
246
    except urllib2.HTTPError:
 
247
      if lock.locked():
 
248
        self.all_changes[name] = [_("Changes not found, the server may not be updated yet."), srcpkg]
 
249
    except IOError:
 
250
      if lock.locked():
 
251
        self.all_changes[name] = [_("Failed to download changes. Please check if there is an active internet connection."), srcpkg]
 
252
    if lock.locked():
 
253
      lock.release()
 
254
 
 
255
  def set_changes_buffer(self, changes_buffer, text, name, srcpkg):
 
256
    changes_buffer.set_text("")
 
257
    lines = text.split("\n")
 
258
    if len(lines) == 1:
 
259
      changes_buffer.set_text(text)
 
260
      return
 
261
    
 
262
    for line in lines:
 
263
    
 
264
      end_iter = changes_buffer.get_end_iter()
 
265
      
 
266
      version_match = re.match("^%s \((.*)\)(.*)$" % (srcpkg), line)
 
267
      #bullet_match = re.match("^.*[\*-]", line)
 
268
      author_match = re.match("^.*--.*<.*@.*>.*$", line)
 
269
      if version_match:
 
270
        version = version_match.group(1)
 
271
        version_text = _("Version %s: \n") % version
 
272
        changes_buffer.insert_with_tags_by_name(end_iter, version_text, "versiontag")
 
273
      # mvo: disabled for now as it does not catch multi line entries
 
274
      #      (see ubuntu #7034 for rational)
 
275
      #elif bullet_match and not author_match:
 
276
      #  bullet_text = "    " + line + "\n"
 
277
      #  changes_buffer.insert(end_iter, bullet_text)
 
278
      elif (author_match):
 
279
        pass
 
280
        #chanages_buffer.insert(end_iter, "\n")
 
281
      else:
 
282
        changes_buffer.insert(end_iter, line+"\n")
 
283
        
 
284
 
 
285
  def cursor_changed(self, widget):
 
286
    tuple = widget.get_cursor()
 
287
    path = tuple[0]
 
288
    # check if we have a path at all
 
289
    if path == None:
 
290
      return
 
291
    model = widget.get_model()
 
292
    iter = model.get_iter(path)
 
293
 
 
294
    # set descr
 
295
    long_desc = model.get_value(iter, 5)
 
296
    if long_desc == None:
 
297
      return
 
298
    desc_buffer = self.DescView.get_buffer()
 
299
    desc_buffer.set_text(utf8(long_desc))
 
300
 
 
301
    # now do the changelog
 
302
    name = model.get_value(iter, 2)
 
303
    if name == None:
 
304
      return
 
305
 
 
306
    changes_buffer = self.ChangesView.get_buffer()
 
307
    
 
308
    # check if we have the changes already
 
309
    if self.all_changes.has_key(name):
 
310
      changes = self.all_changes[name]
 
311
      self.set_changes_buffer(changes_buffer, changes[0], name, changes[1])
 
312
    else:
 
313
      if self.expander.get_expanded():
 
314
        self.treeview.set_sensitive(False)
 
315
        self.Glade.get_widget("hbox_footer").set_sensitive(False)
 
316
        lock = thread.allocate_lock()
 
317
        lock.acquire()
 
318
        t=thread.start_new_thread(self.get_changelog,(name,lock))
 
319
        changes_buffer.set_text(_("Downloading changes..."))
 
320
        button = self.Glade.get_widget("button_cancel_dl_changelog")
 
321
        button.show()
 
322
        id = button.connect("clicked",
 
323
                            lambda w,lock: lock.release(), lock)
 
324
        # wait for the dl-thread
 
325
        while lock.locked():
 
326
          time.sleep(0.05)
 
327
          while gtk.events_pending():
 
328
            gtk.main_iteration()
 
329
        # download finished (or canceld, or time-out)
 
330
        button.hide()
 
331
        button.disconnect(id);
 
332
        self.treeview.set_sensitive(True)
 
333
        self.Glade.get_widget("hbox_footer").set_sensitive(True)
 
334
 
 
335
    if self.all_changes.has_key(name):
 
336
      changes = self.all_changes[name]
 
337
      self.set_changes_buffer(changes_buffer, changes[0], name, changes[1])
 
338
 
 
339
  def remove_update(self, pkg):
 
340
    name = pkg.name
 
341
    if name in self.packages:
 
342
      self.packages.remove(name)
 
343
      self.dl_size -= pkg.size
 
344
      if len(self.packages) == 0:
 
345
        self.installbutton.set_sensitive(False)
 
346
    self.update_count()
 
347
 
 
348
  def add_update(self, pkg):
 
349
    name = pkg.name
 
350
    if name not in self.packages:
 
351
      self.packages.append(name)
 
352
      self.dl_size += pkg.size
 
353
      if len(self.packages) > 0:
 
354
        self.installbutton.set_sensitive(True)
 
355
    self.update_count()
 
356
 
 
357
  def update_count(self):
 
358
    text = "%i (%s)" % (len(self.packages),
 
359
                            apt_pkg.SizeToStr(self.dl_size))
 
360
    self.NumUpdates.set_text(text)
 
361
 
 
362
  def activate_details(self, expander, data):
 
363
    expanded = self.expander.get_expanded()
 
364
    self.gconfclient.set_bool("/apps/update-manager/show_details",expanded)
 
365
    if expanded:
 
366
      self.cursor_changed(self.treeview)
 
367
 
 
368
  def run_synaptic(self, id, action, lock):
 
369
    apt_pkg.PkgSystemUnLock()
 
370
    cmd = ["/usr/sbin/synaptic", "--hide-main-window",  "--non-interactive",
 
371
           "--plug-progress-into", "%s" % (id) ]
 
372
    if action == INSTALL:
 
373
      cmd.append("--set-selections")
 
374
      cmd.append("--progress-str")
 
375
      cmd.append("%s" % _("The updates are being applied."))
 
376
      cmd.append("--finish-str")
 
377
      cmd.append("%s" %  _("Upgrade finished"))
 
378
      proc = subprocess.Popen(cmd, stdin=subprocess.PIPE)
 
379
      f = proc.stdin
 
380
      for s in self.packages:
 
381
        f.write("%s\tinstall\n" % s)
 
382
      f.close()
 
383
      proc.wait()
 
384
    elif action == UPDATE:
 
385
      cmd.append("--update-at-startup")
 
386
      subprocess.call(cmd)
 
387
    else:
 
388
      print "run_synaptic() called with unknown action"
 
389
      sys.exit(1)
 
390
 
 
391
    # use this once gksudo does propper reporting
 
392
    #if os.geteuid() != 0:
 
393
    #  if os.system("gksudo  /bin/true") != 0:
 
394
    #    return
 
395
    #  cmd = "sudo " + cmd;
 
396
    lock.release()
 
397
 
 
398
  def plug_removed(self, w, (win,socket)):
 
399
    #print "plug_removed"
 
400
    # plug was removed, but we don't want to get it removed, only hiden
 
401
    # unti we get more 
 
402
    win.hide()
 
403
    return True
 
404
 
 
405
  def plug_added(self, sock, win):
 
406
    win.show()
 
407
    while gtk.events_pending():
 
408
      gtk.main_iteration()
 
409
 
 
410
  def on_button_reload_clicked(self, widget):
 
411
    #print "on_button_reload_clicked"
 
412
    self.invoke_manager(UPDATE)
 
413
 
 
414
  def on_button_help_clicked(self, widget):
 
415
    gnome.help_display_desktop(self.gnome_program, "update-manager", "update-manager", "")
 
416
 
 
417
  def on_button_install_clicked(self, widget):
 
418
    #print "on_button_install_clicked"
 
419
    self.invoke_manager(INSTALL)
 
420
 
 
421
  def invoke_manager(self, action):
 
422
    # check first if no other package manager is runing
 
423
    import struct, fcntl
 
424
    lock = os.path.dirname(apt_pkg.Config.Find("Dir::State::status"))+"/lock"
 
425
    lock_file= open(lock)
 
426
    flk=struct.pack('hhllhl',fcntl.F_WRLCK,0,0,0,0,0)
 
427
    try:
 
428
      rv = fcntl.fcntl(lock_file, fcntl.F_GETLK, flk)
 
429
    except IOError:
 
430
      print "Error getting lockstatus"
 
431
      raise
 
432
    locked = struct.unpack('hhllhl', rv)[0]
 
433
    if locked != fcntl.F_UNLCK:
 
434
      msg=("<big><b>%s</b></big>\n\n%s"%(_("Another package manager is "
 
435
                                           "running"),
 
436
                                         _("You can run only one "
 
437
                                           "package management application "
 
438
                                           "at the same time. Please close "
 
439
                                           "this other application first.")));
 
440
      dialog = gtk.MessageDialog(self.main_window, 0, gtk.MESSAGE_ERROR,
 
441
                                 gtk.BUTTONS_OK,"")
 
442
      dialog.set_markup(msg)
 
443
      dialog.run()
 
444
      dialog.destroy()
 
445
      return
 
446
 
 
447
    # don't display apt-listchanges, we already showed the changelog
 
448
    os.environ["APT_LISTCHANGES_FRONTEND"]="none"
 
449
 
 
450
    # set window to insensitive
 
451
    self.main_window.set_sensitive(False)
 
452
    # create a progress window that will swallow the synaptic progress bars
 
453
    win = gtk.Window()
 
454
    if action==UPDATE:
 
455
      win.set_title(_("Updating package list..."))
 
456
    else:
 
457
      win.set_title(_("Installing updates..."))
 
458
    win.set_border_width(6)
 
459
    win.set_transient_for(self.main_window)
 
460
    win.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
 
461
    win.resize(400,200)
 
462
    win.set_resizable(False)
 
463
    # prevent the window from closing with the delete button (there is
 
464
    # a cancel button in the window)
 
465
    win.connect("delete_event", lambda e,w: True);
 
466
    
 
467
    # create the socket
 
468
    socket = gtk.Socket()
 
469
    socket.show()
 
470
    win.add(socket)
 
471
 
 
472
    socket.connect("plug-added", self.plug_added, win)
 
473
    socket.connect("plug-removed", self.plug_removed, (win,socket))
 
474
    lock = thread.allocate_lock()
 
475
    lock.acquire()
 
476
    t = thread.start_new_thread(self.run_synaptic,(socket.get_id(),action,lock))
 
477
    while lock.locked():
 
478
      while gtk.events_pending():
 
479
        gtk.main_iteration()
 
480
      time.sleep(0.05)
 
481
    win.destroy()
 
482
    while gtk.events_pending():
 
483
      gtk.main_iteration()
 
484
    self.fillstore()
 
485
    self.main_window.set_sensitive(True)    
 
486
 
 
487
  def toggled(self, renderer, path_string):
 
488
    """ a toggle button in the listview was toggled """
 
489
    iter = self.store.get_iter_from_string(path_string)
 
490
    if self.store.get_value(iter, LIST_INSTALL):
 
491
      self.store.set_value(iter, LIST_INSTALL, False)
 
492
      self.remove_update(self.store.get_value(iter, LIST_PKG))
 
493
    else:
 
494
      self.store.set_value(iter, LIST_INSTALL, True)
 
495
      self.add_update(self.store.get_value(iter, LIST_PKG))
 
496
 
 
497
 
 
498
  def exit(self):
 
499
    """ exit the application, save the state """
 
500
    self.save_state()
 
501
    gtk.main_quit()
 
502
    sys.exit(0)
 
503
 
 
504
  def save_state(self):
 
505
    """ save the state  (window-size for now) """
 
506
    (x,y) = self.main_window.get_size()
 
507
    self.gconfclient.set_pair("/apps/update-manager/window_size",
 
508
                              gconf.VALUE_INT, gconf.VALUE_INT, x, y)
 
509
 
 
510
  def restore_state(self):
 
511
    """ restore the state (window-size for now) """
 
512
    expanded = self.gconfclient.get_bool("/apps/update-manager/show_details")
 
513
    self.expander.set_expanded(expanded)
 
514
    (x,y) = self.gconfclient.get_pair("/apps/update-manager/window_size",
 
515
                                      gconf.VALUE_INT, gconf.VALUE_INT)
 
516
    if x > 0 and y > 0:
 
517
      self.main_window.resize(x,y)
 
518
 
 
519
  def on_button_preferences_clicked(self, widget):
 
520
    """ start gnome-software preferences """
 
521
    # args: "-n" means we take care of the reloading of the
 
522
    # package list ourself
 
523
    apt_pkg.PkgSystemUnLock()
 
524
    args = ['/usr/bin/gnome-software-properties', '-n']
 
525
    child = subprocess.Popen(args)
 
526
    self.main_window.set_sensitive(False)
 
527
    res = None
 
528
    while res == None:
 
529
      res = child.poll()
 
530
      time.sleep(0.05)
 
531
      while gtk.events_pending():
 
532
        gtk.main_iteration()
 
533
    # repository information changed, call "reload"
 
534
    try:
 
535
        apt_pkg.PkgSystemLock()
 
536
    except SystemError:
 
537
        print "Error geting the cache"
 
538
    apt_pkg.PkgSystemLock()
 
539
    if res > 0:
 
540
      self.on_button_reload_clicked(None)
 
541
    self.main_window.set_sensitive(True)
 
542
 
 
543
  def __init__(self, download_changes_at_startup=False):
 
544
 
 
545
    self.gnome_program = gnome.init("update-manager", "0.39")
 
546
 
 
547
    self.download_changes_at_startup = download_changes_at_startup
 
548
    self.packages = []
 
549
    self.dl_size = 0
 
550
    self.all_changes = {}
 
551
    self.dist = self.get_dist()
 
552
    if os.path.exists("../data/update-manager.glade"):
 
553
      self.Glade = gtk.glade.XML("../data/update-manager.glade")
 
554
    else:
 
555
      self.Glade = gtk.glade.XML("@prefix@/share/update-manager/update-manager.glade")
 
556
 
 
557
    self.NumUpdates = self.Glade.get_widget("num_updates")
 
558
    self.main_window = self.Glade.get_widget("MainWindow")
 
559
    self.main_window.connect("delete_event", lambda w, ev: self.exit())
 
560
    self.DescView = self.Glade.get_widget("descview")
 
561
    self.ChangesView = self.Glade.get_widget("textview_changes")
 
562
    changes_buffer = self.ChangesView.get_buffer()
 
563
    changes_buffer.create_tag("versiontag", weight=pango.WEIGHT_BOLD)
 
564
    self.expander = self.Glade.get_widget("expander_details")
 
565
    self.expander.connect("notify::expanded", self.activate_details)
 
566
   
 
567
    self.installbutton = self.Glade.get_widget("button_install")
 
568
    self.Glade.signal_connect("on_button_install_clicked",
 
569
                              self.on_button_install_clicked)
 
570
    self.Glade.signal_connect("on_button_close_clicked",
 
571
                              lambda w: self.exit())
 
572
    self.Glade.signal_connect("on_button_reload_clicked",
 
573
                              self.on_button_reload_clicked)
 
574
    self.Glade.signal_connect("on_button_preferences_clicked",
 
575
                              self.on_button_preferences_clicked)
 
576
    self.Glade.signal_connect("on_button_help_clicked",
 
577
                              self.on_button_help_clicked)
 
578
 
 
579
    self.treeview = self.Glade.get_widget("updatelist")
 
580
 
 
581
    self.store = gtk.ListStore(gobject.TYPE_BOOLEAN, str, str, str, str, str,
 
582
                               gobject.TYPE_PYOBJECT)
 
583
    self.treeview.set_model(self.store)
 
584
    self.treeview.set_headers_clickable(True);
 
585
 
 
586
    self.treeview.connect('cursor-changed', self.cursor_changed)
 
587
 
 
588
    tr = gtk.CellRendererText()
 
589
    tr.set_property("xpad", 10)
 
590
    tr.set_property("ypad", 10)
 
591
    cr = gtk.CellRendererToggle()
 
592
    cr.set_property("activatable", True)
 
593
    cr.set_property("xpad", 10)
 
594
    cr.connect("toggled", self.toggled)
 
595
    self.cb = gtk.TreeViewColumn("Install", cr, active=LIST_INSTALL)
 
596
    c0 = gtk.TreeViewColumn("Name", tr, markup=LIST_CONTENTS)
 
597
    c0.set_resizable(True)
 
598
    major,minor,patch = gtk.pygtk_version
 
599
    if (major >= 2) and (minor >= 5):
 
600
      self.cb.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
 
601
      self.cb.set_fixed_width(30)
 
602
      c0.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
 
603
      c0.set_fixed_width(100)
 
604
      #self.treeview.set_fixed_height_mode(True)
 
605
 
 
606
    self.treeview.append_column(self.cb)
 
607
    self.cb.set_visible(False);
 
608
    self.treeview.append_column(c0)
 
609
    self.treeview.set_search_column(LIST_NAME)  
 
610
    #self.treeview.append_column(c1)
 
611
    #self.treeview.append_column(c2)
 
612
    #self.treeview.set_headers_visible(False)
 
613
    # set expander to last position
 
614
 
 
615
    # proxy stuff
 
616
    SYNAPTIC_CONF_FILE = "%s/.synaptic/synaptic.conf" % pwd.getpwuid(0)[5]
 
617
    if os.path.exists(SYNAPTIC_CONF_FILE):
 
618
      cnf = apt_pkg.newConfiguration()
 
619
      apt_pkg.ReadConfigFile(cnf, SYNAPTIC_CONF_FILE)
 
620
      use_proxy = cnf.FindB("Synaptic::useProxy", False)
 
621
      if use_proxy:
 
622
        proxy_host = cnf.Find("Synaptic::httpProxy")
 
623
        proxy_port = str(cnf.FindI("Synaptic::httpProxyPort"))
 
624
        if proxy_host and proxy_port:
 
625
          proxy_support = urllib2.ProxyHandler({"http":"http://%s:%s" % (proxy_host, proxy_port)})
 
626
          opener = urllib2.build_opener(proxy_support)
 
627
          urllib2.install_opener(opener)
 
628
 
 
629
    self.gconfclient = gconf.client_get_default()
 
630
    # restore state
 
631
    self.restore_state()
 
632
 
 
633
  def fillstore(self):
 
634
    if self.download_changes_at_startup:
 
635
      dialog = self.Glade.get_widget("dialog_fetching")
 
636
      dialog.set_transient_for(self.main_window)
 
637
      dialog.set_modal(True)
 
638
      progress = self.Glade.get_widget("progressbar_fetching")
 
639
      dialog.show()
 
640
      while gtk.events_pending():
 
641
        gtk.main_iteration()
 
642
 
 
643
    # clean most objects
 
644
    self.packages = []
 
645
    self.dl_size = 0
 
646
    self.all_changes = {}
 
647
    self.store.clear()
 
648
    self.initCache()
 
649
    self.list = UpdateList(self.main_window)
 
650
 
 
651
    # fill them again
 
652
    self.list.update(self.cache, self.records, self.depcache)
 
653
    if self.list.num_updates < 1:
 
654
      # set the label and treeview and hide the checkbox column
 
655
      self.cb.set_visible(False)
 
656
      self.expander.hide()
 
657
      label = self.Glade.get_widget("label_header")
 
658
      text = "<big><b>%s</b></big>\n\n%s" % (_("Your system is up-to-date!"),
 
659
                                             _("There are no updates available."))
 
660
      label.set_markup(text)
 
661
      self.store.append([False, _("Your system is up-to-date!"), None, None, None, None, None])
 
662
      # make sure no install is possible
 
663
      self.installbutton.set_sensitive(False)
 
664
    else:
 
665
      self.cb.set_visible(True)
 
666
      self.expander.show()
 
667
      self.treeview.set_headers_visible(False)
 
668
      label = self.Glade.get_widget("label_header")
 
669
      text = _("<big><b>Available Updates</b></big>\n"
 
670
               "\n"
 
671
               "The following packages are found to be upgradable. You can upgrade them by "
 
672
               "using the Install button.")
 
673
      label.set_markup(text)
 
674
      i=0
 
675
      for pkg in self.list.pkgs:
 
676
        if self.download_changes_at_startup:
 
677
          progress.set_fraction(float(i)/len(self.list.pkgs))
 
678
          while gtk.events_pending():
 
679
            gtk.main_iteration()
 
680
          lock = thread.allocate_lock()
 
681
          self.all_changes[pkg.name] = self.get_changelog(pkg.name,lock)
 
682
 
 
683
        name = xml.sax.saxutils.escape(pkg.name)
 
684
        summary = xml.sax.saxutils.escape(pkg.shortdesc)
 
685
        contents = "<big><b>%s</b></big>\n<small>%s\n\n" % (name, summary)
 
686
        contents = contents + _("New version: %s") % (pkg.version) + "</small>"
 
687
 
 
688
        iter = self.store.append([True, contents, pkg.name, pkg.shortdesc, pkg.version, pkg.longdesc, pkg])
 
689
        self.add_update(pkg)
 
690
        i = i + 1
 
691
 
 
692
    if self.download_changes_at_startup:
 
693
      dialog.hide()
 
694
 
 
695
    self.update_count()
 
696
    return False
 
697
 
 
698
  # FIXME: use lsb-release binary and cache the result
 
699
  def get_dist(self):
 
700
    f = open("/etc/lsb-release", "r")
 
701
    lines = f.readlines()
 
702
    for line in lines:
 
703
      key, value = line.split("=")
 
704
      if (key == "DISTRIB_CODENAME"):
 
705
        return value[:-1]
 
706
    f.close()
 
707
 
 
708
  def current_dist_not_supported(self, name):
 
709
    #print name
 
710
    msg = "<big><b>%s</b></big>\n\n%s" % (_("Your distribution is no longer supported"), _("Please upgrade to a newer version of Ubuntu Linux. The version you are running will no longer get security fixes or other critical updates. Please see http://www.ubuntulinux.org for upgrade information."))
 
711
    dialog = gtk.MessageDialog(self.main_window, 0, gtk.MESSAGE_WARNING,
 
712
                               gtk.BUTTONS_OK,"")
 
713
    dialog.set_markup(msg)
 
714
    dialog.run()
 
715
    dialog.destroy()
 
716
    
 
717
 
 
718
  def new_dist_available(self, name):
 
719
    #print name
 
720
    # check if the user already knowns about this dist
 
721
    seen = self.gconfclient.get_string("/apps/update-manager/seen_dist")
 
722
    if name == seen:
 
723
      return
 
724
    
 
725
    msg = "<big><b>%s</b></big>\n\n%s" % (_("There is a new release of Ubuntu available!"), _("A new release with the codename '%s' is available. Please see http://www.ubuntulinux.org/ for upgrade instructions.") % name)
 
726
    dialog = gtk.MessageDialog(self.main_window, 0, gtk.MESSAGE_INFO,
 
727
                               gtk.BUTTONS_CLOSE, "")
 
728
    dialog.set_markup(msg)
 
729
    check = gtk.CheckButton(_("Never show this message again"))
 
730
    check.show()
 
731
    dialog.vbox.pack_start(check)
 
732
    dialog.run()
 
733
    if check.get_active():
 
734
      self.gconfclient.set_string("/apps/update-manager/seen_dist",name)
 
735
    dialog.destroy()
 
736
    
 
737
  # code that does the meta release file checking
 
738
  def check_meta_release(self):
 
739
    #print "check_meta_release" 
 
740
    current_dist = self.dist
 
741
    dists = {}
 
742
    if self.metarelease_information != None:
 
743
      #print "meta_release found (current_dist: %s)" % (current_dist)
 
744
      # we have a meta-release file
 
745
      current_dist_date = 0
 
746
      current_dist_supported = False
 
747
      new_dist_available = False
 
748
      # parse it
 
749
      index_tag = apt_pkg.ParseTagFile(self.metarelease_information)
 
750
      step_result = index_tag.Step()
 
751
      while step_result:
 
752
        if index_tag.Section.has_key("Dist"):
 
753
          dist = index_tag.Section["Dist"]
 
754
          date = time.mktime(rfc822.parsedate(index_tag.Section["Date"]))
 
755
          dists[dist] = date
 
756
          if dist == current_dist:
 
757
            current_dist_supported = str_to_bool(index_tag.Section["Supported"])
 
758
            current_dist_date = time.mktime(rfc822.parsedate(index_tag.Section["Date"]))
 
759
        step_result = index_tag.Step()
 
760
      # check for newer dists
 
761
      new_dist = ""
 
762
      found = False
 
763
      for dist in dists:
 
764
        if dist == current_dist:
 
765
          found = True
 
766
        if dists[dist] > current_dist_date and not dist == current_dist:
 
767
          new_dist = dist
 
768
          current_dist_date = dists[dist]
 
769
 
 
770
      # we know nothing about the installed distro, so we just return
 
771
      # silently
 
772
      if not found:
 
773
        return False
 
774
      
 
775
      # only warn if unsupported and a new dist is available (because 
 
776
      # the development version is also unsupported)
 
777
      if new_dist != "" and not current_dist_supported:
 
778
        self.current_dist_not_supported(new_dist)
 
779
      elif new_dist != "":
 
780
        self.new_dist_available(new_dist)
 
781
      # don't run this event again
 
782
      return False
 
783
    # we have no information about the meta-release, so run it again
 
784
    return True
 
785
 
 
786
  # the network thread that tries to fetch the meta-index file
 
787
  def get_meta_release(self):
 
788
    lastmodified = 0
 
789
    req = urllib2.Request(METARELEASE_URI)
 
790
    if os.access(METARELEASE_FILE, os.W_OK):
 
791
      lastmodified = os.stat(METARELEASE_FILE).st_mtime
 
792
    if lastmodified > 0:
 
793
      req.add_header("If-Modified-Since", lastmodified)
 
794
    try:
 
795
      uri=urllib2.urlopen(req)
 
796
      f=open(METARELEASE_FILE,"w+")
 
797
      for line in uri.readlines():
 
798
        f.write(line)
 
799
      f.flush()
 
800
      f.seek(0,0)
 
801
      self.metarelease_information=f
 
802
      uri.close()
 
803
    except urllib2.URLError:
 
804
      pass
 
805
 
 
806
  # fixme: we should probably abstract away all the stuff from libapt
 
807
  def initCache(self): 
 
808
    # get the lock
 
809
    try:
 
810
        apt_pkg.PkgSystemLock()
 
811
    except SystemError:
 
812
        d = gtk.MessageDialog(parent=self.main_window,
 
813
                              flags=gtk.DIALOG_MODAL,
 
814
                              type=gtk.MESSAGE_ERROR,
 
815
                              buttons=gtk.BUTTONS_OK)
 
816
        d.set_markup("<big><b>%s</b></big>\n\n%s" % (
 
817
            _("Unable to get exclusive lock"),
 
818
            _("This usually means that another package management "
 
819
              "application (like apt-get or aptitude) already running. "
 
820
              "Please close that application first")))
 
821
        res = d.run()
 
822
        d.destroy()
 
823
        sys.exit()
 
824
 
 
825
    self.cache = apt_pkg.GetCache()
 
826
    #apt_pkg.Config.Set("Debug::pkgPolicy","1")
 
827
    self.depcache = apt_pkg.GetDepCache(self.cache)
 
828
    self.depcache.ReadPinFile()
 
829
    if os.path.exists(SYNAPTIC_PINFILE):
 
830
      self.depcache.ReadPinFile(SYNAPTIC_PINFILE)
 
831
    self.depcache.Init()
 
832
    self.records = apt_pkg.GetPkgRecords(self.cache)
 
833
    
 
834
 
 
835
  def main(self):
 
836
    # FIXME: stat a check update thread 
 
837
    self.metarelease_information = None
 
838
    t=thread.start_new_thread(self.get_meta_release, ())
 
839
    gobject.timeout_add(1000, self.check_meta_release)
 
840
    #self.get_meta_release()
 
841
    
 
842
    self.store.append([True, _("Initializing and getting list of updates..."),
 
843
                       None, None, None, None, None])
 
844
 
 
845
    while gtk.events_pending():
 
846
      gtk.main_iteration()
 
847
 
 
848
    # global init of apt, FIXME: move all the apt details in it's own class
 
849
    apt_pkg.init()
 
850
    self.fillstore()
 
851
    gtk.main()
 
852
 
 
853
 
 
854
if __name__ == "__main__":
 
855
 
 
856
  APP="update-manager"
 
857
  DIR="@prefix@/share/locale"
 
858
  gettext.bindtextdomain(APP, DIR)
 
859
  gettext.textdomain(APP)
 
860
  gtk.glade.bindtextdomain(APP, DIR)
 
861
  gtk.glade.textdomain(APP)
 
862
  _ = gettext.gettext
 
863
  if os.geteuid() != 0:
 
864
    dialog = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
 
865
                               _("You need to be root to run this program"))
 
866
    dialog.run()
 
867
    dialog.destroy()
 
868
    sys.exit(1)
 
869
 
 
870
  if (len(sys.argv) > 1) and (sys.argv[1].strip() == "--download-changes-at-startup"):
 
871
    updatemanager = UpdateManager(True)
 
872
  else:
 
873
    updatemanager = UpdateManager()
 
874
  updatemanager.main()