#!/usr/bin/python2.4 import pygtk pygtk.require('2.0') import gtk import gtk.gdk import gtk.glade import gobject import apt import apt_pkg import sys from UpdateManager.Common.SimpleGladeApp import SimpleGladeApp from UpdateManager.GtkProgress import GtkOpProgress from SoftwareProperties.aptsources import SourcesList, SourceEntry from gettext import gettext as _ class MyCache(apt.Cache): @property def requiredDownload(self): pm = apt_pkg.GetPackageManager(self._depcache) fetcher = apt_pkg.GetAcquire() pm.GetArchives(fetcher, self._list, self._records) return fetcher.FetchNeeded class DistUpgradeView(object): " abstraction for the upgrade view " def __init__(self): pass def getOpCacheProgress(self): " return a OpProgress() subclass for the given graphic" return apt.progress.OpProgress() def getFetchProgress(self): " return a fetch progress object " return apt.progress.FetchProgress() def getInstallProgress(self): " return a install progress object " return apt.progress.InstallProgress() def updateStatus(self, msg): """ update the current status of the distUpgrade based on the current view """ pass def confirmChanges(self, changes, downloadSize): """ display the list of changed packages (apt.Package) and return if the user confirms them """ self.toInstall = [] self.toUpgrade = [] self.toRemove = [] for pkg in changes: if pkg.markedInstall: self.toInstall.append(pkg.name) elif pkg.markedUpgrade: self.toUpgrade.append(pkg.name) elif pkg.markedDelete: self.toRemove.append(pkg.name) # no downgrades, re-installs assert(len(self.toInstall)+len(self.toUpgrade)+len(self.toRemove) == len(changes)) def askYesNoQuestion(self, summary, msg): pass def error(self, summary, msg): pass class GtkDistUpgradeView(DistUpgradeView,SimpleGladeApp): " gtk frontend of the distUpgrade tool " class GtkFetchProgressAdapter(apt.progress.FetchProgress): # FIXME: we really should have some sort of "we are at step" # xy in the gui # FIXME2: we need to thing about mediaCheck here too def __init__(self, parent): # if this is set to false the download will cancel self.status = parent.label_status self.progress = parent.progressbar_cache def start(self): self.progress.show() self.progress.set_fraction(0) def stop(self): self.progress.hide() def pulse(self): # FIXME: move the status_str and progress_str into python-apt # (python-apt need i18n first for this) apt.progress.FetchProgress.pulse(self) if self.currentCPS > 0: self.status.set_text(_("Download rate: %s/s - %s remaining" % (apt_pkg.SizeToStr(self.currentCPS), apt_pkg.TimeToStr(self.eta)))) else: self.status.set_text(_("Download rate: unkown")) self.progress.set_fraction(self.percent/100.0) currentItem = self.currentItems + 1 if currentItem > self.totalItems: currentItem = self.totalItems self.progress.set_text(_("Downloading file %li of %li" % (currentItem, self.totalItems))) while gtk.events_pending(): gtk.main_iteration() return True def __init__(self): # FIXME: i18n must be somewhere relative do this dir SimpleGladeApp.__init__(self, "DistUpgrade.glade", None, domain="update-manager") self._opCacheProgress = GtkOpProgress(self.progressbar_cache) self._fetchProgress = self.GtkFetchProgressAdapter(self) # details dialog self.details_list = gtk.ListStore(gobject.TYPE_STRING) column = gtk.TreeViewColumn("") render = gtk.CellRendererText() column.pack_start(render, True) column.add_attribute(render, "markup", 0) self.treeview_details.append_column(column) self.treeview_details.set_model(self.details_list) def getFetchProgress(self): return self._fetchProgress def getOpCacheProgress(self): return self._opCacheProgress def updateStatus(self, msg): self.label_status.set_markup("%s" % msg) def error(self, summary, msg): dialog = gtk.MessageDialog(self.window_main, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,"") msg="%s\n\n%s" % (summary,msg) dialog.set_markup(msg) dialog.vbox.set_spacing(6) dialog.run() dialog.destroy() return False def confirmChanges(self, changes, downloadSize): # FIXME: add a whitelist here for packages that we expect to be # removed (how to calc this automatically?) DistUpgradeView.confirmChanges(self, changes,downloadSize) msg = _("%s packages are going to be removed.\n" "%s packages are going to be newly installed.\n" "%s packages are going to be upgraded.\n\n" "%s needs to be fetched" % (len(self.toRemove), len(self.toInstall), len(self.toUpgrade), apt_pkg.SizeToStr(downloadSize))) self.label_changes.set_text(msg) # fill in the details self.details_list.clear() for rm in self.toRemove: self.details_list.append([_("To be removed: %s" % rm)]) for inst in self.toInstall: self.details_list.append([_("To be installed: %s" % inst)]) for up in self.toUpgrade: self.details_list.append([_("To be upgraded: %s" % up)]) res = self.dialog_changes.run() self.dialog_changes.hide() if res == gtk.RESPONSE_YES: return True return False def askYesNoQuestion(self, summary, msg): msg = "%s\n\n%s" % (summary,msg) dialog = gtk.MessageDialog(parent=self.window_main, flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_QUESTION, buttons=gtk.BUTTONS_YES_NO) dialog.set_markup(msg) res = dialog.run() dialog.destroy() if res == gtk.RESPONSE_YES: return True return False class DistUpgradeControler(object): def __init__(self, distUpgradeView): self._view = distUpgradeView self._view.updateStatus(_("Reading cache")) self._cache = MyCache(self._view.getOpCacheProgress()) def sanityCheck(self): if self._cache._depcache.BrokenCount > 0: # FIXME: we more helpful here and offer to actually fix the # system self._view.error(_("Broken packages"), _("Your system contains broken packages. " "Please fix them first using synaptic or " "apt-get before proceeding.")) return False # FIXME: check for ubuntu-desktop, kubuntu-dekstop, edubuntu-desktop return True def updateSourcesList(self, fromDist, to): sources = SourcesList() # this must map, i.e. second in "from" must be the second in "to" # (but they can be different, so in theory we could exchange # component names here) fromDists = [fromDist, fromDist+"-security", fromDist+"-updates", fromDist+"-backports" ] toDists = [to, to+"-security", to+"-updates", to+"-backports" ] # list of valid mirrors that we can add valid_mirrors = ["http://archive.ubuntu.com/ubuntu", "http://security.ubuntu.com/ubuntu"] # look over the stuff we have foundToDist = False for entry in sources: # check if it's a mirror (or offical site) for mirror in valid_mirrors: if sources.is_mirror(mirror,entry.uri): if entry.dist in toDists: # so the sources.list is already set to the new # distro foundToDist = True elif entry.dist in fromDists: foundToDist = True entry.dist = toDists[fromDists.index(entry.dist)] else: # disable all entries that are official but don't # point to the "from" dist entry.disabled = True # it can only be one valid mirror, so we can break here break else: # disable non-official entries that point to dist if entry.dist == fromDist: entry.disabled = True if not foundToDist: # FIXME: offer to write a new sources.list entry return self._view.error(_("No valid entry found"), _("While scaning your repository " "information no valid entry for " "the upgrade was found.\n")) # write (well, backup first ;) ! backup_ext = ".distUpgrade" sources.backup(backup_ext) sources.save() # re-check if the written sources are valid, if not revert and # bail out try: sourceslist = apt_pkg.GetPkgSourceList() sourceslist.ReadMainList() except SystemError: sources.restoreBackup(backup_ext) self._view.error(_("Repository information invalid"), _("Upgrading the repository information " "resulted in a invalid file. Please " "report this as a bug.")) return False return True def doPreUpgrade(self): # FIXME: check out what packages are downloadable etc to # compare the list after the update again pass def doUpdate(self): self._cache._list.ReadMainList() progress = self._view.getFetchProgress() self._cache.update(progress) def askDistUpgrade(self): self._cache.upgrade(True) changes = self._cache.getChanges() res = self._view.confirmChanges(changes,self._cache.requiredDownload) return res def doDistUpgrade(self): fprogress = self._view.getFetchProgress() iprogress = self._view.getInstallProgress() self._cache.commit(fprogress,iprogress) def doPostUpgrade(self): # FIXME: check out what packages are cruft now pass def askForReboot(self): return self._view.askYesNoQuestion(_("Reboot required"), _("The upgrade is finished now. " "A reboot is required to " "now, do you want to do this " "now?")) # this is the core def breezyUpgrade(self): # sanity check (check for ubuntu-desktop, brokenCache etc) self._view.updateStatus(_("Checking the system")) if not self.sanityCheck(): sys.exit(1) # update sources.list self._view.updateStatus(_("Updating repository information")) if not self.updateSourcesList(fromDist="hoary",to="breezy"): sys.exit(1) # then update the package index files self.doUpdate() # then open the cache (again) self._view.updateStatus(_("Reading cache")) self._cache = MyCache(self._view.getOpCacheProgress()) # do pre-upgrade stuff self.doPreUpgrade() # calc the dist-upgrade and see if the removals are ok/expected # do the dist-upgrade if not self.askDistUpgrade(): sys.exit(1) self.doDistUpgrade() # do post-upgrade stuff self.doPostUpgrade() # done, ask for reboot if self.askForReboot(): subprocess.call(["reboot"]) def run(self): self.breezyUpgrade() if __name__ == "__main__": view = GtkDistUpgradeView() app = DistUpgradeControler(view) app.run()