#!/usr/bin/python -tt # # Copyright 2005-2007 Red Hat, Inc. # # Jeremy Katz # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2 only # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import os, sys # # Python gettext: # import gettext import logging try: import gtk import gtk.glade import gtk.gdk as gdk import gobject except Exception, e: print >> sys.stderr, "Unable to import modules. Maybe you're not running under X?" sys.exit(1) from rhpl.exception import installExceptionHandler from rhpl.translate import _, N_, textdomain from rpmUtils.miscutils import compareEVR import rpmUtils.arch import yum.misc from yum.packages import parsePackages, YumLocalPackage from yum import logginglevels import rpm from pirut import * from pirut.constants import * from pirut.Errors import * if os.path.exists("data/single.glade"): gladefn = "data/single.glade" else: gladefn = "/usr/share/pirut/ui/single.glade" # # Python gettext: # t = gettext.translation(I18N_DOMAIN, "/usr/share/locale", fallback = True) # _ = t.lgettext class SinglePackageInstaller(GraphicalYumBase): def __init__(self): self.xml = gtk.glade.XML(gladefn, domain=I18N_DOMAIN) self.mainwin = self.xml.get_widget("InstallerWindow") self._connectSignals() self._createPackageStore() self.mainwin.connect("delete_event", self.quit) # note that nothing which takes "time" should be called here! GraphicalYumBase.__init__(self, False) self.unsignedok = True self.repos_setup = False self._enabledRepos = self.repos.listEnabled() map(lambda x: x.disable(), self._enabledRepos) def _connectSignals(self): sigs = { "on_InstallerWindow_destroy": self.quit, "on_quitButton_clicked": self.quit, "on_applyButton_clicked": self._apply } self.xml.signal_autoconnect(sigs) def _createPackageStore(self): self.store = gtk.ListStore(gobject.TYPE_PYOBJECT) tree = self.xml.get_widget("packageList") tree.set_model(self.store) column = gtk.TreeViewColumn(None, None) column.set_clickable(True) txtr = gtk.CellRendererText() column.pack_start(txtr, True) column.set_cell_data_func(txtr, self.get_pkg_info) tree.append_column(column) def get_pkg_info(self, col, cell, model, i): pkg = model.get_value(i, 0) nevra = "%s" %(pkg,) desc = pkg.returnSimple('summary') text = "%s\n%s" %(nevra, desc) text.strip() cell.set_property("markup", text) def doRefresh(self, *args): self._busyCursor() self.populatePackages() self._normalCursor() def logDialog(self, msg): print msg d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, msg) d.show_all() d.run() d.destroy() def errorDialog(self, msg): print msg d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, msg) d.show_all() d.run() d.destroy() def _populateLocalPackages(self, pkgs): self.store.clear() updates = [] installs = [] for pkg in pkgs: try: po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg) except yum.Errors.MiscError, e: log = logging.getLogger("yum") log.warn("Cannot open file: %s. Skipping." %(pkg,)) continue if po.hdr[rpm.RPMTAG_SOURCEPACKAGE] == 1: # ugh. self.logDialog(_("Cannot install source packages.")) continue inst = self.rpmdb.searchNevra(name=po.name) for instpo in inst: (n,a,e,v,r) = po.pkgtup (inn,ia,ie,iv,ir) = instpo.pkgtup rc = compareEVR((ie,iv,ir), (e,v,r)) if rc < 0: # update if n not in self.conf.exactarchlist or a == ia: updates.append((po, instpo)) else: self.logDialog(_("Can't install a different arch of %s than already installed.") %(pkg,)) continue elif rc == 0: # same, ignore self.logDialog("%s is already installed" %(pkg,)) elif rc > 0: self.logDialog("A newer version than %s is already installed" %(pkg,)) if len(inst) == 0: installs.append(po) for po in installs: self.localPackages.append(po) self.tsInfo.addInstall(po = po) self.store.append([po]) for (po, oldpo) in updates: self.localPackages.append(po) self.tsInfo.addUpdate(po, oldpo) self.store.append([po]) def _populateRepoPackages(self, pkgs): self.store.clear() self._ensure_repos() avail = self.pkgSack.returnPackages() toBeInstalled = {} # keyed on name updates = [] # FIXME: # Parsing command line options that aren't local packages gets complex, # partly because the user has the option of specifying or not specifying # arch and version. The code below is lifted verbatim from the cli.py # implementation of 'yum install'. It perhaps should be moved into the # yum core in some fashion. # ######################################## # BEGIN CUT-AND-PASTE from cli.py # self.verbose_logger.log(logginglevels.INFO_2, _('Parsing package install arguments')) for arg in pkgs: arglist = [arg] exactmatch, matched, unmatched = parsePackages(avail, arglist, casematch=1) if len(unmatched) > 0: # if we get back anything in unmatched, check it for a virtual-provide arg = unmatched[0] #only one in there self.verbose_logger.debug('Checking for virtual provide or file-provide for %s', arg) try: mypkg = self.returnPackageByDep(arg) except yum.Errors.YumBaseError, e: self.errorDialog(_('No Match for argument: %s') % arg) # Continuing on and installing only some packages from the requested # set is probably not useful self.quit() else: arg = '%s:%s-%s-%s.%s' % (mypkg.epoch, mypkg.name, mypkg.version, mypkg.release, mypkg.arch) emtch, mtch, unmtch = parsePackages(avail, [arg]) exactmatch.extend(emtch) matched.extend(mtch) installable = yum.misc.unique(exactmatch + matched) exactarchlist = self.conf.exactarchlist # we look through each returned possibility and rule out the # ones that we obviously can't use for pkg in installable: if self.rpmdb.installed(po=pkg): self.verbose_logger.log(logginglevels.DEBUG_3, 'Package %s is already installed, skipping', pkg) continue # everything installed that matches the name installedByKey = self.rpmdb.searchNevra(name=pkg.name) comparable = [] for instpo in installedByKey: if rpmUtils.arch.isMultiLibArch(instpo.arch) == rpmUtils.arch.isMultiLibArch(pkg.arch): comparable.append(instpo) else: self.verbose_logger.log(logginglevels.DEBUG_3, 'Discarding non-comparable pkg %s.%s', instpo.name, instpo.arch) continue # go through each package if len(comparable) > 0: for instpo in comparable: if pkg.EVR > instpo.EVR: # we're newer - this is an update, pass to them if instpo.name in exactarchlist: if pkg.arch == instpo.arch: updates.append((pkg, instpo)) else: updates.append((pkg, instpo)) elif pkg.EVR == instpo.EVR: # same, ignore continue elif pkg.EVR < instpo.EVR: # lesser, check if the pkgtup is an exactmatch # if so then add it to be installed # if it can be multiply installed # this is where we could handle setting # it to be an 'oldpackage' revert. if pkg in exactmatch and self.allowedMultipleInstalls(pkg): if not toBeInstalled.has_key(pkg.name): toBeInstalled[pkg.name] = [] toBeInstalled[pkg.name].append(pkg) else: # we've not got any installed that match n or n+a self.verbose_logger.log(logginglevels.DEBUG_1, 'No other %s installed, adding to list for potential install', pkg.name) if not toBeInstalled.has_key(pkg.name): toBeInstalled[pkg.name] = [] toBeInstalled[pkg.name].append(pkg) # this is where I could catch the installs of compat and multilib # arches on a single yum install command. pkglist = [] for name in toBeInstalled.keys(): pkglist.extend(self.bestPackagesFromList(toBeInstalled[name])) # # END CUT-AND-PASTE from cli.py ######################################### for po in pkglist: self.tsInfo.addInstall(po = po) self.store.append([po]) for (po, oldpo) in updates: self.tsInfo.addUpdate(po, oldpo) self.store.append([po]) def populatePackages(self): locals = [] others = [] # at some point, we should maybe be smarter here for pkg in sys.argv[1:]: if os.path.exists(pkg): locals.append(pkg) else: others.append(pkg) self.doRefreshRepos(progress = False) if len(locals) > 0: self._populateLocalPackages(locals) if len(others) > 0: self._populateRepoPackages(others) if not hasattr(self, "tsInfo") or len(self.tsInfo) <= 0: self.errorDialog(_("No packages were given for installation.")) self.quit() def _busyCursor(self): self.mainwin.window.set_cursor(gdk.Cursor(gdk.WATCH)) self.mainwin.set_sensitive(False) def _normalCursor(self): self.mainwin.window.set_cursor(None) self.mainwin.set_sensitive(True) def _ensure_repos(self): if self.repos_setup: return map(lambda x: x.enable(), self._enabledRepos) self.doRefreshRepos() self.repos_setup = True def _apply(self, *args): # do a depsolve to see if we need to grab any other stuff... this is # kind of a hack knowing what resolveDeps does self.populateTs(test=1) deps = self.ts.check() if deps: self._ensure_repos() try: output = self.applyChanges(self.mainwin) except PirutError: self.quit() except OSError: d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL, gtk.BUTTONS_OK, _("Unable to install software. Error " "opening files.")) d.show_all() d.run() d.destroy() self.quit() d = PirutDetailsDialog(self.mainwin, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, _("Software installed successfully.")) if output: d.format_secondary_text("Some warnings were given.") d.set_details(buffer = outputDictAsTextBuffer(output)) d.run() d.destroy() self.quit() def run(self): self.mainwin.show_all() while gtk.events_pending(): gtk.main_iteration() self.doRefresh() gtk.main() def main(): textdomain(I18N_DOMAIN) gtk.glade.bindtextdomain(I18N_DOMAIN, "/usr/share/locale") try: # right now, we have to run privileged... if os.getuid() != 0: raise PirutError(_("Must be run as root.")) pkginst = SinglePackageInstaller() except PirutError, e: startupError(e) pkginst.run() if __name__ == "__main__": installExceptionHandler("pirut", "") main()