home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / lib / python2.4 / site-packages / UpdateManager / UpdateManager.py < prev    next >
Encoding:
Python Source  |  2006-08-24  |  34.4 KB  |  955 lines

  1. # UpdateManager.py 
  2. #  
  3. #  Copyright (c) 2004-2006 Canonical
  4. #                2004 Michiel Sikkes
  5. #                2005 Martin Willemoes Hansen
  6. #  
  7. #  Author: Michiel Sikkes <michiel@eyesopened.nl>
  8. #          Michael Vogt <mvo@debian.org>
  9. #          Martin Willemoes Hansen <mwh@sysrq.dk>
  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. #  This program is distributed in the hope that it will be useful,
  15. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17. #  GNU General Public License for more details.
  18. #  You should have received a copy of the GNU General Public License
  19. #  along with this program; if not, write to the Free Software
  20. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  21. #  USA
  22.  
  23. import pygtk
  24. pygtk.require('2.0')
  25. import gtk
  26. import gtk.gdk
  27. import gtk.glade
  28. try:
  29.     import gconf
  30. except:
  31.     import fakegconf as gconf
  32. import gobject
  33. import apt
  34. import apt_pkg
  35. import gettext
  36. import copy
  37. import string
  38. import sys
  39. import os
  40. import os.path
  41. import urllib2
  42. import re
  43. import locale
  44. import tempfile
  45. import pango
  46. import subprocess
  47. import pwd
  48. import time
  49. import thread
  50. import xml.sax.saxutils
  51. from Common.HelpViewer import HelpViewer
  52.  
  53. import dbus
  54. import dbus.service
  55. import dbus.glib
  56.  
  57. from gettext import gettext as _
  58.  
  59. from Common.utils import *
  60. from Common.SimpleGladeApp import SimpleGladeApp
  61. from DistUpgradeFetcher import DistUpgradeFetcher
  62. import GtkProgress
  63.  
  64. from MetaRelease import Dist, MetaRelease
  65.  
  66. #import pdb
  67.  
  68. # FIXME:
  69. # - kill "all_changes" and move the changes into the "Update" class
  70.  
  71. # list constants
  72. (LIST_INSTALL, LIST_CONTENTS, LIST_NAME, LIST_PKG) = range(4)
  73.  
  74. # actions for "invoke_manager"
  75. (INSTALL, UPDATE) = range(2)
  76.  
  77. SYNAPTIC_PINFILE = "/var/lib/synaptic/preferences"
  78.  
  79. CHANGELOGS_URI="http://changelogs.ubuntu.com/changelogs/pool/%s/%s/%s/%s_%s/changelog"
  80.  
  81.  
  82. class MyCache(apt.Cache):
  83.     def __init__(self, progress):
  84.         apt.Cache.__init__(self, progress)
  85.         self._initDepCache()
  86.         assert self._depcache.BrokenCount == 0 and self._depcache.DelCount == 0
  87.         self.all_changes = {}
  88.     def _initDepCache(self):
  89.         #apt_pkg.Config.Set("Debug::pkgPolicy","1")
  90.         #self.depcache = apt_pkg.GetDepCache(self.cache)
  91.         #self._depcache = apt_pkg.GetDepCache(self._cache)
  92.         self._depcache.ReadPinFile()
  93.         if os.path.exists(SYNAPTIC_PINFILE):
  94.             self._depcache.ReadPinFile(SYNAPTIC_PINFILE)
  95.         self._depcache.Init()
  96.     def clean(self):
  97.         self._initDepCache()
  98.     def saveDistUpgrade(self):
  99.         """ this functions mimics a upgrade but will never remove anything """
  100.         self._depcache.Upgrade(True)
  101.         if self._depcache.DelCount > 0:
  102.             self.clean()
  103.         assert self._depcache.BrokenCount == 0 and self._depcache.DelCount == 0
  104.         self._depcache.Upgrade()
  105.  
  106.     def get_changelog(self, name, lock):
  107.         # don't touch the gui in this function, it needs to be thread-safe
  108.         pkg = self[name]
  109.  
  110.     # get the src package name
  111.         srcpkg = pkg.sourcePackageName
  112.  
  113.         # assume "main" section 
  114.         src_section = "main"
  115.         # use the section of the candidate as a starting point
  116.         section = pkg._depcache.GetCandidateVer(pkg._pkg).Section
  117.  
  118.         # get the source version, start with the binaries version
  119.         binver = pkg.candidateVersion
  120.         #print "bin: %s" % binver
  121.         try:
  122.             # try to get the source version of the pkg, this differs
  123.             # for some (e.g. libnspr4 on ubuntu)
  124.             # this feature only works if the correct deb-src are in the 
  125.             # sources.list
  126.             # otherwise we fall back to the binary version number
  127.             srcrecords = apt_pkg.GetPkgSrcRecords()
  128.             srcrec = srcrecords.Lookup(srcpkg)
  129.             if srcrec:
  130.                 srcver = srcrecords.Version
  131.                 if apt_pkg.VersionCompare(binver, srcver) > 0:
  132.                     srcver = binver
  133.                 #print "srcver: %s" % srcver
  134.                 section = srcrecords.Section
  135.                 #print "srcsect: %s" % section
  136.         except SystemError, e:
  137.             srcver = binver
  138.  
  139.         l = section.split("/")
  140.         if len(l) > 1:
  141.             src_section = l[0]
  142.  
  143.         # lib is handled special
  144.         prefix = srcpkg[0]
  145.         if srcpkg.startswith("lib"):
  146.             prefix = "lib" + srcpkg[3]
  147.  
  148.         # stip epoch
  149.         l = string.split(srcver,":")
  150.         if len(l) > 1:
  151.             srcver = "".join(l[1:])
  152.  
  153.         try:
  154.             uri = CHANGELOGS_URI % (src_section,prefix,srcpkg,srcpkg, srcver)
  155.             # print "Trying: %s " % uri
  156.             changelog = urllib2.urlopen(uri)
  157.             #print changelog.read()
  158.             # do only get the lines that are new
  159.             alllines = ""
  160.             regexp = "^%s \((.*)\)(.*)$" % (re.escape(srcpkg))
  161.  
  162.             i=0
  163.             while True:
  164.                 line = changelog.readline()
  165.                 if line == "":
  166.                     break
  167.                 match = re.match(regexp,line)
  168.                 if match:
  169.                     # FIXME: the installed version can have a epoch, but th
  170.                     #        changelog does not have one, we do a dumb
  171.                     #        approach here and just strip it away, but I'm
  172.                     #        sure that this can lead to problems
  173.                     installed = pkg.installedVersion
  174.                     if installed and ":" in installed:
  175.                         installed = installed.split(":",1)[1]
  176.                     if installed and \
  177.                         apt_pkg.VersionCompare(match.group(1),installed)<=0:
  178.                         break
  179.                 # EOF (shouldn't really happen)
  180.                 alllines = alllines + line
  181.  
  182.             # Print an error if we failed to extract a changelog
  183.             if len(alllines) == 0:
  184.                 alllines = _("The list of changes is not available")
  185.             # only write if we where not canceld
  186.             if lock.locked():
  187.                 self.all_changes[name] = [alllines, srcpkg]
  188.         except urllib2.HTTPError:
  189.             if lock.locked():
  190.                 self.all_changes[name] = [_("The list of changes is not "
  191.                                             "available yet. Please try again "
  192.                                             "later."), srcpkg]
  193.         except IOError:
  194.             if lock.locked():
  195.                 self.all_changes[name] = [_("Failed to download the list "
  196.                                             "of changes. Please "
  197.                                             "check your Internet "
  198.                                             "connection."), srcpkg]
  199.         if lock.locked():
  200.             lock.release()
  201.  
  202. class UpdateList:
  203.   class UpdateOrigin:
  204.     def __init__(self, desc, importance):
  205.       self.packages = []
  206.       self.importance = importance
  207.       self.description = desc
  208.  
  209.   def __init__(self, parent_window):
  210.     # a map of packages under their origin
  211.     pipe = os.popen("lsb_release -c -s")
  212.     dist = pipe.read().strip()
  213.     del pipe
  214.  
  215.     templates = [("%s-security" % dist, "Ubuntu", _("Important security updates"
  216.                                                     " of Ubuntu"), 10),
  217.                  ("%s-updates" % dist, "Ubuntu", _("Recommended updates of "
  218.                                                    "Ubuntu"), 9),
  219.                  ("%s-proposed" % dist, "Ubuntu", _("Proposed updates for Ubuntu"), 8),
  220.                  ("%s-backports" % dist, "Ubuntu", _("Backports of Ubuntu"), 7),
  221.                  (dist, "Ubuntu", _("Updates of Ubuntu"), 6)]
  222.  
  223.     self.pkgs = {}
  224.     self.matcher = {}
  225.     self.num_updates = 0
  226.     self.parent_window = parent_window
  227.     for (origin, archive, desc, importance) in templates:
  228.         self.matcher[(origin, archive)] = self.UpdateOrigin(desc, importance)
  229.     self.unknown_origin = self.UpdateOrigin(_("Other updates"), -1)
  230.  
  231.   def update(self, cache):
  232.     held_back = []
  233.     broken = []
  234.  
  235.     # do the upgrade
  236.     cache.saveDistUpgrade()
  237.  
  238.     # sort by origin
  239.     for pkg in cache:
  240.       if pkg.markedUpgrade or pkg.markedInstall:
  241.         # TRANSLATORS: updates from an 'unknown' origin
  242.         originstr = _("Other updates")
  243.         for aorigin in pkg.candidateOrigin:
  244.           archive = aorigin.archive
  245.           origin = aorigin.origin
  246.         if self.matcher.has_key((archive,origin)) and aorigin.trusted:
  247.           origin_node = self.matcher[(archive,origin)]
  248.         else:
  249.           origin_node = self.unknown_origin
  250.         if not self.pkgs.has_key(origin_node):
  251.           self.pkgs[origin_node] = []
  252.         self.pkgs[origin_node].append(pkg)
  253.         self.num_updates = self.num_updates + 1
  254.       elif pkg.isUpgradable:
  255.           held_back.append(pkg.name)
  256.     for l in self.pkgs.keys():
  257.       self.pkgs[l].sort(lambda x,y: cmp(x.name,y.name))
  258.  
  259.     # check if we have held-back something
  260.     if cache._depcache.KeepCount > 0:
  261.       #print "WARNING, keeping packages"
  262.       msg = ("<big><b>%s</b></big>\n\n%s" % \
  263.             (_("Cannot install all available updates"),
  264.              _("Some updates require the removal of further software. "
  265.                "Use the function \"Mark All Upgrades\" of the package manager "
  266.            "\"Synaptic\" or run \"sudo apt-get dist-upgrade\" in a "
  267.            "terminal to update your system completely.")))
  268.       dialog = gtk.MessageDialog(self.parent_window, 0, gtk.MESSAGE_INFO,
  269.                                  gtk.BUTTONS_CLOSE,"")
  270.       dialog.set_default_response(gtk.RESPONSE_OK)
  271.       dialog.set_markup(msg)
  272.       dialog.set_title("")
  273.       dialog.vbox.set_spacing(6)
  274.       label = gtk.Label(_("The following updates will be skipped:"))
  275.       label.set_alignment(0.0,0.5)
  276.       dialog.set_border_width(6)
  277.       label.show()
  278.       dialog.vbox.pack_start(label)
  279.       scroll = gtk.ScrolledWindow()
  280.       scroll.set_size_request(-1,200)
  281.       scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
  282.       text = gtk.TextView()
  283.       text.set_editable(False)
  284.       text.set_cursor_visible(False)
  285.       buf = text.get_buffer()
  286.       held_back.sort()
  287.       buf.set_text("\n".join(held_back))
  288.       scroll.add(text)
  289.       dialog.vbox.pack_start(scroll)
  290.       scroll.show_all()
  291.       dialog.run()
  292.       dialog.destroy()
  293.  
  294.  
  295. class UpdateManagerDbusControler(dbus.service.Object):
  296.     """ this is a helper to provide the UpdateManagerIFace """
  297.     def __init__(self, parent, bus_name,
  298.                  object_path='/org/freedesktop/UpdateManagerObject'):
  299.         dbus.service.Object.__init__(self, bus_name, object_path)
  300.         self.parent = parent
  301.  
  302.     @dbus.service.method('org.freedesktop.UpdateManagerIFace')
  303.     def bringToFront(self):
  304.         self.parent.window_main.present()
  305.         return True
  306.  
  307. class UpdateManager(SimpleGladeApp):
  308.  
  309.   def __init__(self, datadir):
  310.     self.setupDbus()
  311.     gtk.window_set_default_icon_name("update-manager")
  312.  
  313.     self.datadir = datadir
  314.     SimpleGladeApp.__init__(self, datadir+"glade/UpdateManager.glade",
  315.                             None, domain="update-manager")
  316.  
  317.     self.image_logo.set_from_icon_name("update-manager", gtk.ICON_SIZE_DIALOG)
  318.     self.window_main.set_sensitive(False)
  319.     self.window_main.grab_focus()
  320.     self.button_close.grab_focus()
  321.  
  322.     self.packages = []
  323.     self.dl_size = 0
  324.  
  325.     # create text view
  326.     changes_buffer = self.textview_changes.get_buffer()
  327.     changes_buffer.create_tag("versiontag", weight=pango.WEIGHT_BOLD)
  328.  
  329.     # expander
  330.     self.expander_details.connect("notify::expanded", self.activate_details)
  331.  
  332.     # useful exit stuff
  333.     self.window_main.connect("delete_event", self.close)
  334.     self.button_close.connect("clicked", lambda w: self.exit())
  335.  
  336.     # the treeview (move into it's own code!)
  337.     self.store = gtk.ListStore(gobject.TYPE_BOOLEAN, str, str, gobject.TYPE_PYOBJECT)
  338.     self.treeview_update.set_model(self.store)
  339.     self.treeview_update.set_headers_clickable(True);
  340.  
  341.     tr = gtk.CellRendererText()
  342.     tr.set_property("xpad", 6)
  343.     tr.set_property("ypad", 6)
  344.     cr = gtk.CellRendererToggle()
  345.     cr.set_property("activatable", True)
  346.     cr.set_property("xpad", 6)
  347.     cr.connect("toggled", self.toggled)
  348.  
  349.     column_install = gtk.TreeViewColumn("Install", cr)
  350.     column_install.set_cell_data_func (cr, self.install_column_view_func)
  351.     column = gtk.TreeViewColumn("Name", tr, markup=LIST_CONTENTS)
  352.     column.set_cell_data_func (tr, self.package_column_view_func)
  353.     column.set_resizable(True)
  354.     major,minor,patch = gtk.pygtk_version
  355.     if (major >= 2) and (minor >= 5):
  356.       column_install.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
  357.       column_install.set_fixed_width(30)
  358.       column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
  359.       column.set_fixed_width(100)
  360.       #self.treeview_update.set_fixed_height_mode(True)
  361.  
  362.     self.treeview_update.append_column(column_install)
  363.     column_install.set_visible(True)
  364.     self.treeview_update.append_column(column)
  365.     self.treeview_update.set_search_column(LIST_NAME)
  366.     self.treeview_update.connect("button-press-event", self.show_context_menu)
  367.  
  368.  
  369.     # proxy stuff
  370.     # FIXME: move this into it's own function
  371.     SYNAPTIC_CONF_FILE = "%s/.synaptic/synaptic.conf" % pwd.getpwuid(0)[5]
  372.     if os.path.exists(SYNAPTIC_CONF_FILE):
  373.       cnf = apt_pkg.newConfiguration()
  374.       apt_pkg.ReadConfigFile(cnf, SYNAPTIC_CONF_FILE)
  375.       use_proxy = cnf.FindB("Synaptic::useProxy", False)
  376.       if use_proxy:
  377.         proxy_host = cnf.Find("Synaptic::httpProxy")
  378.         proxy_port = str(cnf.FindI("Synaptic::httpProxyPort"))
  379.         if proxy_host and proxy_port:
  380.       # FIXME: set the proxy for libapt here as well (e.g. for the
  381.       #        DistUpgradeFetcher
  382.           proxy_support = urllib2.ProxyHandler({"http":"http://%s:%s" % (proxy_host, proxy_port)})
  383.           opener = urllib2.build_opener(proxy_support)
  384.           urllib2.install_opener(opener)
  385.       # install a proxy environment too
  386.       if not os.environ.has_key("http_proxy"):
  387.           os.putenv("http_proxy",
  388.                 "http://%s:%s/" % (proxy_host, proxy_port))
  389.  
  390.     # setup the help viewer and disable the help button if there
  391.     # is no viewer available
  392.     self.help_viewer = HelpViewer("update-manager")
  393.     if self.help_viewer.check() == False:
  394.         self.button_help.set_sensitive(False)
  395.  
  396.     self.gconfclient = gconf.client_get_default()
  397.     # restore state
  398.     self.restore_state()
  399.     self.window_main.show()
  400.  
  401.   def header_column_func(self, cell_layout, renderer, model, iter):
  402.     pkg = model.get_value(iter, LIST_PKG)
  403.     if pkg == None:
  404.       renderer.set_property("sensitive", False)
  405.     else:
  406.       renderer.set_property("sensitive", True)
  407.  
  408.   def install_column_view_func(self, cell_layout, renderer, model, iter):
  409.     self.header_column_func(cell_layout, renderer, model, iter)
  410.     pkg = model.get_value(iter, LIST_PKG)
  411.     to_install = model.get_value(iter, LIST_INSTALL)
  412.     renderer.set_property("active", to_install)
  413.     # hide it if we are only a header line
  414.     renderer.set_property("visible", pkg != None)
  415.       
  416.   def package_column_view_func(self, cell_layout, renderer, model, iter):
  417.     self.header_column_func(cell_layout, renderer, model, iter)
  418.       
  419.   def setupDbus(self):
  420.     """ this sets up a dbus listener if none is installed alread """
  421.     # check if there is another g-a-i already and if not setup one
  422.     # listening on dbus
  423.     try:
  424.         bus = dbus.SessionBus()
  425.     except:
  426.         print "warning: could not initiate dbus"
  427.         return
  428.     proxy_obj = bus.get_object('org.freedesktop.UpdateManager', 
  429.                                '/org/freedesktop/UpdateManagerObject')
  430.     iface = dbus.Interface(proxy_obj, 'org.freedesktop.UpdateManagerIFace')
  431.     try:
  432.         iface.bringToFront()
  433.         #print "send bringToFront"
  434.         sys.exit(0)
  435.     except dbus.DBusException, e:
  436.          print "no listening object (%s) "% e
  437.          bus_name = dbus.service.BusName('org.freedesktop.UpdateManager',bus)
  438.          self.dbusControler = UpdateManagerDbusControler(self, bus_name)
  439.  
  440.  
  441.   def on_checkbutton_reminder_toggled(self, checkbutton):
  442.     self.gconfclient.set_bool("/apps/update-manager/remind_reload",
  443.                               not checkbutton.get_active())
  444.  
  445.   def close(self, widget, data=None):
  446.     if self.window_main.get_property("sensitive") is False:
  447.         return True
  448.     else:
  449.         self.exit()
  450.  
  451.   
  452.   def set_changes_buffer(self, changes_buffer, text, name, srcpkg):
  453.     changes_buffer.set_text("")
  454.     lines = text.split("\n")
  455.     if len(lines) == 1:
  456.       changes_buffer.set_text(text)
  457.       return
  458.     
  459.     for line in lines:
  460.       end_iter = changes_buffer.get_end_iter()
  461.       version_match = re.match(r'^%s \((.*)\)(.*)\;.*$' % re.escape(srcpkg), line)
  462.       #bullet_match = re.match("^.*[\*-]", line)
  463.       author_match = re.match("^.*--.*<.*@.*>.*$", line)
  464.       if version_match:
  465.         version = version_match.group(1)
  466.     upload_archive = version_match.group(2).strip()
  467.         version_text = _("Version %s: \n") % version
  468.         changes_buffer.insert_with_tags_by_name(end_iter, version_text, "versiontag")
  469.       elif (author_match):
  470.         pass
  471.       else:
  472.         changes_buffer.insert(end_iter, line+"\n")
  473.         
  474.  
  475.   def on_treeview_update_cursor_changed(self, widget):
  476.     tuple = widget.get_cursor()
  477.     path = tuple[0]
  478.     # check if we have a path at all
  479.     if path == None:
  480.       return
  481.     model = widget.get_model()
  482.     iter = model.get_iter(path)
  483.  
  484.     # set descr
  485.     pkg = model.get_value(iter, LIST_PKG)
  486.     if pkg == None or pkg.description == None:
  487.       changes_buffer = self.textview_changes.get_buffer()
  488.       changes_buffer.set_text("")
  489.       desc_buffer = self.textview_descr.get_buffer()
  490.       desc_buffer.set_text("")
  491.       self.notebook_details.set_sensitive(False)
  492.       return
  493.     long_desc = pkg.description
  494.     self.notebook_details.set_sensitive(True)
  495.     # Skip the first line - it's a duplicate of the summary
  496.     i = long_desc.find("\n")
  497.     long_desc = long_desc[i+1:]
  498.     # do some regular expression magic on the description
  499.     # Add a newline before each bullet
  500.     p = re.compile(r'^(\s|\t)*(\*|0|-)',re.MULTILINE)
  501.     long_desc = p.sub('\n*', long_desc)
  502.     # replace all newlines by spaces
  503.     p = re.compile(r'\n', re.MULTILINE)
  504.     long_desc = p.sub(" ", long_desc)
  505.     # replace all multiple spaces by newlines
  506.     p = re.compile(r'\s\s+', re.MULTILINE)
  507.     long_desc = p.sub("\n", long_desc)
  508.  
  509.     desc_buffer = self.textview_descr.get_buffer()
  510.     desc_buffer.set_text(long_desc)
  511.  
  512.     # now do the changelog
  513.     name = model.get_value(iter, LIST_NAME)
  514.     if name == None:
  515.       return
  516.  
  517.     changes_buffer = self.textview_changes.get_buffer()
  518.     
  519.     # check if we have the changes already
  520.     if self.cache.all_changes.has_key(name):
  521.       changes = self.cache.all_changes[name]
  522.       self.set_changes_buffer(changes_buffer, changes[0], name, changes[1])
  523.     else:
  524.       if self.expander_details.get_expanded():
  525.         lock = thread.allocate_lock()
  526.         lock.acquire()
  527.         t=thread.start_new_thread(self.cache.get_changelog,(name,lock))
  528.         changes_buffer.set_text(_("Downloading the list of changes..."))
  529.         button = self.button_cancel_dl_changelog
  530.         button.show()
  531.         id = button.connect("clicked",
  532.                             lambda w,lock: lock.release(), lock)
  533.         # wait for the dl-thread
  534.         while lock.locked():
  535.           time.sleep(0.05)
  536.           while gtk.events_pending():
  537.             gtk.main_iteration()
  538.         # download finished (or canceld, or time-out)
  539.         button.hide()
  540.         button.disconnect(id);
  541.  
  542.     if self.cache.all_changes.has_key(name):
  543.       changes = self.cache.all_changes[name]
  544.       self.set_changes_buffer(changes_buffer, changes[0], name, changes[1])
  545.  
  546.   def show_context_menu(self, widget, event):
  547.     """
  548.     Show a context menu if a right click was performed on an update entry
  549.     """
  550.     if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3:
  551.         menu = gtk.Menu()
  552.         item_select_none = gtk.MenuItem(_("Select _None"))
  553.         item_select_none.connect("activate", self.select_none_updgrades)
  554.         menu.add(item_select_none)
  555.         if self.list.num_updates == 0 or len(self.packages) == 0:
  556.             item_select_none.set_property("sensitive", False)
  557.         item_select_all = gtk.MenuItem(_("Select _All"))
  558.         item_select_all.connect("activate", self.select_all_updgrades)
  559.         menu.add(item_select_all)
  560.         if self.list.num_updates == len(self.packages) or\
  561.            self.list.num_updates == 0:
  562.             item_select_all.set_property("sensitive", False)
  563.         menu.popup(None, None, None, 0, event.time)
  564.         menu.show_all()
  565.         return True
  566.  
  567.   def select_all_updgrades(self, widget):
  568.     """
  569.     Select all updates
  570.     """
  571.     iter = self.store.get_iter_first()
  572.     while iter != None:
  573.         pkg = self.store.get_value(iter, LIST_PKG)
  574.         if pkg != None:
  575.             self.store.set_value(iter, LIST_INSTALL, True)
  576.             self.add_update(pkg)
  577.         iter = self.store.iter_next(iter)
  578.  
  579.   def select_none_updgrades(self, widget):
  580.     """
  581.     Select none updates
  582.     """
  583.     iter = self.store.get_iter_first()
  584.     while iter != None:
  585.         pkg = self.store.get_value(iter, LIST_PKG)
  586.         if pkg != None:
  587.             self.store.set_value(iter, LIST_INSTALL, False)
  588.             self.remove_update(pkg)
  589.         iter = self.store.iter_next(iter)
  590.  
  591.   def humanize_size(self, bytes):
  592.       """
  593.       Convert a given size in bytes to a nicer better readable unit
  594.       """
  595.       if bytes == 0:
  596.           # TRANSLATORS: download size is 0
  597.           return _("None")
  598.       elif bytes < 1024:
  599.           # TRANSLATORS: download size of very small updates
  600.           return _("1 KB")
  601.       elif bytes < 1024 * 1024:
  602.           # TRANSLATORS: download size of small updates, e.g. "250 KB"
  603.           return locale.format(_("%.0f KB"), bytes/1024)
  604.       else:
  605.           # TRANSLATORS: download size of updates, e.g. "2.3 MB"
  606.           return locale.format(_("%.1f MB"), bytes / 1024 / 1024)
  607.  
  608.   def remove_update(self, pkg):
  609.     name = pkg.name
  610.     if name in self.packages:
  611.       self.packages.remove(name)
  612.       self.dl_size -= pkg.packageSize
  613.       # TRANSLATORS: b stands for Bytes
  614.       self.label_downsize.set_markup(_("Download size: %s" % \
  615.                                      self.humanize_size(self.dl_size)))
  616.       if len(self.packages) == 0:
  617.         self.button_install.set_sensitive(False)
  618.  
  619.   def add_update(self, pkg):
  620.     name = pkg.name
  621.     if name not in self.packages:
  622.       self.packages.append(name)
  623.       self.dl_size += pkg.packageSize
  624.       self.label_downsize.set_markup(_("Download size: %s" % \
  625.                                      self.humanize_size(self.dl_size)))
  626.       if len(self.packages) > 0:
  627.         self.button_install.set_sensitive(True)
  628.  
  629.   def update_count(self):
  630.       """activate or disable widgets and show dialog texts correspoding to
  631.          the number of available updates"""
  632.       if self.list.num_updates == 0:
  633.           text_header= "<big><b>"+_("Your system is up-to-date")+"</b></big>"
  634.           text_download = ""
  635.           self.notebook_details.set_sensitive(False)
  636.           self.treeview_update.set_sensitive(False)
  637.           self.button_install.set_sensitive(False)
  638.           self.label_downsize.set_text=""
  639.           self.button_close.grab_default()
  640.           self.textview_changes.get_buffer().set_text("")
  641.           self.textview_descr.get_buffer().set_text("")
  642.       else:
  643.           text_header = "<big><b>" + \
  644.                         gettext.ngettext("You can install %s update",
  645.                                          "You can install %s updates", 
  646.                                          self.list.num_updates) % \
  647.                                         self.list.num_updates + "</b></big>"
  648.           text_download = _("Download size: %s") % self.humanize_size(self.dl_size)
  649.           self.notebook_details.set_sensitive(True)
  650.           self.treeview_update.set_sensitive(True)
  651.           self.button_install.grab_default()
  652.           self.treeview_update.set_cursor(1)
  653.       self.label_header.set_markup(text_header)
  654.       self.label_downsize.set_markup(text_download)
  655.  
  656.   def activate_details(self, expander, data):
  657.     expanded = self.expander_details.get_expanded()
  658.     self.vbox_updates.set_child_packing(self.expander_details,
  659.                                         expanded,
  660.                                         True,
  661.                                         0,
  662.                                         True)
  663.     self.gconfclient.set_bool("/apps/update-manager/show_details",expanded)
  664.     if expanded:
  665.       self.on_treeview_update_cursor_changed(self.treeview_update)
  666.  
  667.   def run_synaptic(self, id, action, lock):
  668.     try:
  669.       apt_pkg.PkgSystemUnLock()
  670.     except SystemError:
  671.       pass
  672.     cmd = ["gksu", "--desktop", "/usr/share/applications/synaptic.desktop", 
  673.            "--", "/usr/sbin/synaptic", "--hide-main-window",  
  674.            "--non-interactive", "--parent-window-id", "%s" % (id) ]
  675.     if action == INSTALL:
  676.       cmd.append("--progress-str")
  677.       cmd.append("%s" % _("Please wait, this can take some time."))
  678.       cmd.append("--finish-str")
  679.       cmd.append("%s" %  _("Update is complete"))
  680.       f = tempfile.NamedTemporaryFile()
  681.       for s in self.packages:
  682.         f.write("%s\tinstall\n" % s)
  683.       cmd.append("--set-selections-file")
  684.       cmd.append("%s" % f.name)
  685.       f.flush()
  686.       subprocess.call(cmd)
  687.       f.close()
  688.     elif action == UPDATE:
  689.       cmd.append("--update-at-startup")
  690.       subprocess.call(cmd)
  691.     else:
  692.       print "run_synaptic() called with unknown action"
  693.       sys.exit(1)
  694.     lock.release()
  695.  
  696.   def on_button_reload_clicked(self, widget):
  697.     #print "on_button_reload_clicked"
  698.     self.invoke_manager(UPDATE)
  699.  
  700.   def on_button_help_clicked(self, widget):
  701.     self.help_viewer.run()
  702.  
  703.   def on_button_install_clicked(self, widget):
  704.     #print "on_button_install_clicked"
  705.     self.invoke_manager(INSTALL)
  706.  
  707.   def invoke_manager(self, action):
  708.     # check first if no other package manager is runing
  709.  
  710.     # don't display apt-listchanges, we already showed the changelog
  711.     os.environ["APT_LISTCHANGES_FRONTEND"]="none"
  712.  
  713.     # Do not suspend during the update process
  714.     (dev, cookie) = self.inhibit_sleep()
  715.  
  716.     # set window to insensitive
  717.     self.window_main.set_sensitive(False)
  718.     self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
  719.     lock = thread.allocate_lock()
  720.     lock.acquire()
  721.     t = thread.start_new_thread(self.run_synaptic,
  722.                                 (self.window_main.window.xid,action,lock))
  723.     while lock.locked():
  724.       while gtk.events_pending():
  725.         gtk.main_iteration()
  726.       time.sleep(0.05)
  727.     while gtk.events_pending():
  728.       gtk.main_iteration()
  729.     self.fillstore()
  730.  
  731.     # Allow suspend after synaptic is finished
  732.     if cookie != False:
  733.         self.allow_sleep(dev, cookie)
  734.     self.window_main.set_sensitive(True)
  735.     self.window_main.window.set_cursor(None)
  736.  
  737.   def inhibit_sleep(self):
  738.     """Send a dbus signal to gnome-power-manager to not suspend
  739.     the system"""
  740.     try:
  741.       bus = dbus.Bus(dbus.Bus.TYPE_SESSION)
  742.       devobj = bus.get_object('org.gnome.PowerManager', 
  743.                               '/org/gnome/PowerManager')
  744.       dev = dbus.Interface(devobj, "org.gnome.PowerManager")
  745.       cookie = dev.Inhibit('UpdateManager', 'Updating system')
  746.       return (dev, cookie)
  747.     except Exception, e:
  748.       print "could not send the dbus Inhibit signal: %s" % e
  749.       return (False, False)
  750.  
  751.   def allow_sleep(self, dev, cookie):
  752.     """Send a dbus signal to gnome-power-manager to allow a suspending
  753.     the system"""
  754.     dev.UnInhibit(cookie)
  755.  
  756.   def toggled(self, renderer, path):
  757.     """ a toggle button in the listview was toggled """
  758.     iter = self.store.get_iter(path)
  759.     pkg = self.store.get_value(iter, LIST_PKG)
  760.     if pkg is None:
  761.         return
  762.     if self.store.get_value(iter, LIST_INSTALL):
  763.       self.store.set_value(iter, LIST_INSTALL, False)
  764.       self.remove_update(pkg)
  765.     else:
  766.       self.store.set_value(iter, LIST_INSTALL, True)
  767.       self.add_update(pkg)
  768.  
  769.   def on_treeview_update_row_activated(self, treeview, path, column, *args):
  770.       """
  771.       If an update row was activated (by pressing space), toggle the 
  772.       install check box
  773.       """
  774.       self.toggled(None, path)
  775.  
  776.   def exit(self):
  777.     """ exit the application, save the state """
  778.     self.save_state()
  779.     gtk.main_quit()
  780.     sys.exit(0)
  781.  
  782.   def save_state(self):
  783.     """ save the state  (window-size for now) """
  784.     (x,y) = self.window_main.get_size()
  785.     self.gconfclient.set_pair("/apps/update-manager/window_size",
  786.                               gconf.VALUE_INT, gconf.VALUE_INT, x, y)
  787.  
  788.   def restore_state(self):
  789.     """ restore the state (window-size for now) """
  790.     expanded = self.gconfclient.get_bool("/apps/update-manager/show_details")
  791.     self.expander_details.set_expanded(expanded)
  792.     self.vbox_updates.set_child_packing(self.expander_details,
  793.                                         expanded,
  794.                                         True,
  795.                                         0,
  796.                                         True)
  797.     (x,y) = self.gconfclient.get_pair("/apps/update-manager/window_size",
  798.                                       gconf.VALUE_INT, gconf.VALUE_INT)
  799.     if x > 0 and y > 0:
  800.       self.window_main.resize(x,y)
  801.  
  802.   def fillstore(self):
  803.     # use the watch cursor
  804.     self.window_main.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
  805.  
  806.     # clean most objects
  807.     self.packages = []
  808.     self.dl_size = 0
  809.     self.store.clear()
  810.     self.initCache()
  811.     self.list = UpdateList(self.window_main)
  812.  
  813.     # fill them again
  814.     self.list.update(self.cache)
  815.     if self.list.num_updates > 0:
  816.       i=0
  817.       origin_list = self.list.pkgs.keys()
  818.       origin_list.sort(lambda x,y: cmp(x.importance,y.importance))
  819.       origin_list.reverse()
  820.       for origin in origin_list:
  821.         self.store.append([False,'<b><big>%s</big></b>' % origin.description,
  822.                            origin.description, None])
  823.         for pkg in self.list.pkgs[origin]:
  824.           name = xml.sax.saxutils.escape(pkg.name)
  825.           summary = xml.sax.saxutils.escape(pkg.summary)
  826.           contents = "<b>%s</b>\n<small>%s\n" % (name, summary)
  827.           if pkg.installedVersion != None:
  828.               contents += _("From version %s to %s") % \
  829.                            (pkg.installedVersion,
  830.                             pkg.candidateVersion)
  831.           else:
  832.               contents += _("Version %s") % pkg.candidateVersion
  833.           #TRANSLATORS: the b stands for Bytes
  834.           contents += " " + _("(Size: %s)") % self.humanize_size(pkg.packageSize)
  835.           contents += "</small>"
  836.  
  837.           iter = self.store.append([True, contents, pkg.name, pkg])
  838.           self.add_update(pkg)
  839.           i = i + 1
  840.  
  841.     self.update_count()
  842.     # use the normal cursor
  843.     self.window_main.window.set_cursor(None)
  844.     return False
  845.  
  846.   def dist_no_longer_supported(self, meta_release):
  847.     msg = "<big><b>%s</b></big>\n\n%s" % \
  848.           (_("Your distribution is not supported anymore"),
  849.        _("You will not get any further security fixes or critical "
  850.              "updates. "
  851.              "Upgrade to a later version of Ubuntu Linux. See "
  852.              "http://www.ubuntu.com for more information on "
  853.              "upgrading."))
  854.     dialog = gtk.MessageDialog(self.window_main, 0, gtk.MESSAGE_WARNING,
  855.                                gtk.BUTTONS_CLOSE,"")
  856.     dialog.set_title("")
  857.     dialog.set_markup(msg)
  858.     dialog.run()
  859.     dialog.destroy()
  860.     
  861.   def on_button_dist_upgrade_clicked(self, button):
  862.       #print "on_button_dist_upgrade_clicked"
  863.       fetcher = DistUpgradeFetcher(self, self.new_dist)
  864.       fetcher.run()
  865.       
  866.   def new_dist_available(self, meta_release, upgradable_to):
  867.     self.frame_new_release.show()
  868.     self.label_new_release.set_markup(_("<b>New distribution release '%s' is available</b>") % upgradable_to.version)
  869.     self.new_dist = upgradable_to
  870.     
  871.  
  872.   # fixme: we should probably abstract away all the stuff from libapt
  873.   def initCache(self): 
  874.     # get the lock
  875.     try:
  876.         apt_pkg.PkgSystemLock()
  877.     except SystemError, e:
  878.         pass
  879.         #d = gtk.MessageDialog(parent=self.window_main,
  880.         #                      flags=gtk.DIALOG_MODAL,
  881.         #                      type=gtk.MESSAGE_ERROR,
  882.         #                      buttons=gtk.BUTTONS_CLOSE)
  883.         #d.set_markup("<big><b>%s</b></big>\n\n%s" % (
  884.         #    _("Only one software management tool is allowed to "
  885.         #      "run at the same time"),
  886.         #    _("Please close the other application e.g. 'aptitude' "
  887.         #      "or 'Synaptic' first.")))
  888.         #print "error from apt: '%s'" % e
  889.         #d.set_title("")
  890.         #res = d.run()
  891.         #d.destroy()
  892.         #sys.exit()
  893.  
  894.     try:
  895.         progress = GtkProgress.GtkOpProgress(self.dialog_cacheprogress,
  896.                                              self.progressbar_cache,
  897.                                              self.label_cache,
  898.                                              self.window_main)
  899.         self.cache = MyCache(progress)
  900.     except AssertionError:
  901.         # we assert a clean cache
  902.         msg=("<big><b>%s</b></big>\n\n%s"% \
  903.              (_("Software index is broken"),
  904.               _("It is impossible to install or remove any software. "
  905.                 "Please use the package manager \"Synaptic\" or run "
  906.         "\"sudo apt-get install -f\" in a terminal to fix "
  907.         "this issue at first.")))
  908.         dialog = gtk.MessageDialog(self.window_main,
  909.                                    0, gtk.MESSAGE_ERROR,
  910.                                    gtk.BUTTONS_CLOSE,"")
  911.         dialog.set_markup(msg)
  912.         dialog.vbox.set_spacing(6)
  913.         dialog.run()
  914.         dialog.destroy()
  915.         sys.exit(1)
  916.     else:
  917.         progress.hide()
  918.  
  919.   def check_auto_update(self):
  920.       # Check if automatic update is enabled. If not show a dialog to inform
  921.       # the user about the need of manual "reloads"
  922.       remind = self.gconfclient.get_bool("/apps/update-manager/remind_reload")
  923.       if remind == False:
  924.           return
  925.  
  926.       update_days = apt_pkg.Config.FindI("APT::Periodic::Update-Package-Lists")
  927.       if update_days < 1:
  928.           self.dialog_manual_update.set_transient_for(self.window_main)
  929.           res = self.dialog_manual_update.run()
  930.           self.dialog_manual_update.hide()
  931.           if res == gtk.RESPONSE_YES:
  932.               self.on_button_reload_clicked(None)
  933.  
  934.  
  935.   def main(self, options):
  936.     gconfclient = gconf.client_get_default() 
  937.     self.meta = MetaRelease(options.devel_release)
  938.     self.meta.connect("dist_no_longer_supported",self.dist_no_longer_supported)
  939.  
  940.     # check if we are interessted in dist-upgrade information
  941.     # (we are not by default on dapper)
  942.     if options.check_dist_upgrades or \
  943.        gconfclient.get_bool("/apps/update-manager/check_dist_upgrades"):
  944.       self.meta.connect("new_dist_available",self.new_dist_available)
  945.     
  946.     while gtk.events_pending():
  947.       gtk.main_iteration()
  948.  
  949.     self.fillstore()
  950.     self.check_auto_update()
  951.     gtk.main()
  952.