#
# yuminstall.py
#
# Copyright (C) 2005, 2006, 2007  Red Hat, Inc.  All rights reserved.
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

from flags import flags
from errors import *

import sys
import os
import os.path
import shutil
import time
import warnings
import types
import locale
import glob
import tempfile
import itertools
import re


import anaconda_log
import rpm
import rpmUtils
import urlgrabber.progress
import urlgrabber.grabber
from urlgrabber.grabber import URLGrabber, URLGrabError
import yum
import iniparse
from yum.constants import *
from yum.Errors import *
from yum.misc import to_unicode
from yum.yumRepo import YumRepository
from backend import AnacondaBackend
from product import *
from sortedtransaction import SplitMediaTransactionData
from constants import *
from image import *
from compssort import *
import packages

import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z)

import network

# specspo stuff
rpm.addMacro("_i18ndomains", "redhat-dist")

import logging
log = logging.getLogger("anaconda")

import urlparse
urlparse.uses_fragment.append('media')

urlgrabber.grabber.default_grabber.opts.user_agent = "%s (anaconda)/%s" %(productName, productVersion)

import iutil
import isys

def size_string (size):
    def number_format(s):
        return locale.format("%s", s, 1)

    retval = None

    if size > 1024 * 1024:
        size = size / (1024*1024)
        retval = _("%s MB") %(number_format(size),)
    elif size > 1024:
        size = size / 1024
        retval = _("%s KB") %(number_format(size),)
    else:
        retval = P_("%s Byte", "%s Bytes", size) % (number_format(size),)

    return to_unicode(retval)

class AnacondaCallback:

    def __init__(self, ayum, anaconda, instLog, modeText):
        self.repos = ayum.repos
        self.ts = ayum.ts
        self.ayum = ayum

        self.messageWindow = anaconda.intf.messageWindow
        self.pulseWindow = anaconda.intf.progressWindow
        self.progress = anaconda.intf.instProgress
        self.progressWindowClass = anaconda.intf.progressWindow
        self.rootPath = anaconda.rootPath

        self.initWindow = None

        self.progressWindow = None
        self.lastprogress = 0
        self.incr = 20

        self.instLog = instLog
        self.modeText = modeText

        self.openfile = None
        self.inProgressPo = None

    def setSizes(self, numpkgs, totalSize, totalFiles):
        self.numpkgs = numpkgs
        self.totalSize = totalSize
        self.totalFiles = totalFiles

        self.donepkgs = 0
        self.doneSize = 0
        self.doneFiles = 0
        

    def callback(self, what, amount, total, h, user):
        if what == rpm.RPMCALLBACK_TRANS_START:
            # step 6 is the bulk of the ts processing time
            if amount == 6:
                self.progressWindow = \
                    self.progressWindowClass (_("Preparing to install"),
                                              _("Preparing transaction from installation source"),
                                              total)
                self.incr = total / 10

        if what == rpm.RPMCALLBACK_TRANS_PROGRESS:
            if self.progressWindow and amount > self.lastprogress + self.incr:
                self.progressWindow.set(amount)
                self.lastprogress = amount

        if what == rpm.RPMCALLBACK_TRANS_STOP and self.progressWindow:
            self.progressWindow.pop()

        if what == rpm.RPMCALLBACK_INST_OPEN_FILE:
            (hdr, rpmloc) = h
            # hate hate hate at epochs...
            epoch = hdr['epoch']
            if epoch is not None:
                epoch = str(epoch)
            txmbrs = self.ayum.tsInfo.matchNaevr(hdr['name'], hdr['arch'],
                                                 epoch, hdr['version'],
                                                 hdr['release'])
            if len(txmbrs) == 0:
                raise RuntimeError, "Unable to find package %s-%s-%s.%s" %(hdr['name'], hdr['version'], hdr['release'], hdr['arch'])
            po = txmbrs[0].po

            repo = self.repos.getRepo(po.repoid)

            pkgStr = "%s-%s-%s.%s" % (po.name, po.version, po.release, po.arch)
            s = to_unicode(_("<b>Installing %(pkgStr)s</b> (%(size)s)\n")) \
                    % {'pkgStr': pkgStr, 'size': size_string(hdr['size'])}
            summary = to_unicode(gettext.ldgettext("redhat-dist", hdr['summary'] or ""))
            s += summary.strip()
            self.progress.set_label(s)

            self.instLog.write(self.modeText % str(pkgStr))

            self.instLog.flush()
            self.openfile = None

            while self.openfile is None:
                try:
                    fn = repo.getPackage(po)

                    f = open(fn, 'r')
                    self.openfile = f
                except yum.Errors.NoMoreMirrorsRepoError:
                    self.ayum._handleFailure(po)
                except IOError:
                    self.ayum._handleFailure(po)
                except yum.Errors.RepoError, e:
                    continue
            self.inProgressPo = po

            return self.openfile.fileno()

        elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE:
            if self.initWindow:
                self.initWindow.pop()
                self.initWindow = None

            (hdr, rpmloc) = h

            fn = self.openfile.name
            self.openfile.close()
            self.openfile = None

            if os.path.dirname(fn).startswith("%s/var/cache/yum/" % self.rootPath):
                try:
                    os.unlink(fn)
                except OSError as e:
                    log.debug("unable to remove file %s" %(e.strerror,))

            self.donepkgs += 1
            self.doneSize += self.inProgressPo.returnSimple("installedsize") / 1024.0
            self.doneFiles += len(hdr[rpm.RPMTAG_BASENAMES])

            if self.donepkgs <= self.numpkgs:
                self.progress.set_text(P_("Packages completed: "
                                          "%(donepkgs)d of %(numpkgs)d",
                                          "Packages completed: "
                                          "%(donepkgs)d of %(numpkgs)d",
                                          self.numpkgs)
                                       % {'donepkgs': self.donepkgs,
                                          'numpkgs': self.numpkgs})
            self.progress.set_fraction(float(self.doneSize / self.totalSize))
            self.progress.processEvents()

            self.inProgressPo = None

        elif what in (rpm.RPMCALLBACK_UNINST_START,
                      rpm.RPMCALLBACK_UNINST_STOP):
            if self.initWindow is None:
                self.initWindow = self.pulseWindow(_("Finishing upgrade"),
                                                   _("Finishing upgrade process.  This may take a little while."),
                                                   0, pulse=True)
            else:
                self.initWindow.pulse()

        elif what in (rpm.RPMCALLBACK_CPIO_ERROR,
                      rpm.RPMCALLBACK_UNPACK_ERROR,
                      rpm.RPMCALLBACK_SCRIPT_ERROR):
            if not isinstance(h, types.TupleType):
                h = (h, None)

            (hdr, rpmloc) = h

            # If this is a cleanup/remove, then hdr is a string not a header.
            if isinstance(hdr, rpm.hdr):
                name = hdr['name']
            else:
                name = hdr

            # Script errors store whether or not they're fatal in "total".  So,
            # we should only error out for fatal script errors or the cpio and
            # unpack problems.
            if what != rpm.RPMCALLBACK_SCRIPT_ERROR or total:
                self.messageWindow(_("Error Installing Package"),
                    _("A fatal error occurred when installing the %s "
                      "package.  This could indicate errors when reading "
                      "the installation media.  Installation cannot "
                      "continue.") % name,
                    type="custom", custom_icon="error",
                    custom_buttons=[_("_Exit installer")])
                sys.exit(1)

        if self.initWindow is None:
            self.progress.processEvents()

class AnacondaYumRepo(YumRepository):
    def __init__(self, *args, **kwargs):
        YumRepository.__init__(self, *args, **kwargs)
        self.enablegroups = True
        self._anacondaBaseURLs = []

    def needsNetwork(self):
        def _isURL(s):
            return s.startswith("http") or s.startswith("ftp")

        if len(self.baseurl) > 0:
            return len(filter(lambda s: _isURL(s), self.baseurl)) > 0
        elif self.mirrorlist:
            return _isURL(self.mirrorlist)
        else:
            return False

    def dirCleanup(self):
        cachedir = self.getAttribute('cachedir')

        if os.path.isdir(cachedir):
            if not self.needsNetwork() or self.name == "Installation Repo":
                shutil.rmtree(cachedir)
            else:
                if os.path.exists("%s/headers" % cachedir):
                    shutil.rmtree("%s/headers" % cachedir)
                if os.path.exists("%s/packages" % cachedir):
                    shutil.rmtree("%s/packages" % cachedir)

    # needed to store nfs: repo url that yum doesn't know
    def _getAnacondaBaseURLs(self):
        return self._anacondaBaseURLs or self.baseurl or [self.mirrorlist]

    def _setAnacondaBaseURLs(self, value):
        self._anacondaBaseURLs = value

    anacondaBaseURLs = property(_getAnacondaBaseURLs, _setAnacondaBaseURLs,
                                doc="Extends AnacondaYum.baseurl to store non-yum urls:")

class YumSorter(yum.YumBase):
    def _transactionDataFactory(self):
        return SplitMediaTransactionData()

class AnacondaYum(YumSorter):
    def __init__(self, anaconda):
        YumSorter.__init__(self)
        self.anaconda = anaconda
        self._timestamp = None

        self.repoIDcounter = itertools.count()

        # Only needed for hard drive and nfsiso installs.
        self._discImages = {}
        self.isodir = None

        # Only needed for media installs.
        self.currentMedia = None
        self.mediagrabber = None

        # Where is the source media mounted?  This is the directory
        # where Packages/ is located.
        self.tree = "/mnt/source"

        self.macros = {}

        if flags.selinux:
            for directory in ("/tmp/updates",
                        "/etc/selinux/targeted/contexts/files",
                        "/etc/security/selinux/src/policy/file_contexts",
                        "/etc/security/selinux"):
                fn = "%s/file_contexts" %(directory,)
                if os.access(fn, os.R_OK):
                    break
            self.macros["__file_context_path"] = fn
        else:
            self.macros["__file_context_path"]  = "%{nil}"

        self.updates = []
        self.localPackages = []

    def setup(self):
        # yum doesn't understand all our method URLs, so use this for all
        # except FTP and HTTP installs.
        self._baseRepoURL = "file://%s" % self.tree

        while True:
            try:
                self.configBaseURL()
                break
            except SystemError, e:
                self.anaconda.intf.messageWindow(_("Error Setting Up Repository"),
                    _("The following error occurred while setting up the "
                      "installation repository:\n\n%(e)s\n\nPlease provide the "
                      "correct information for installing %(productName)s.")
                    % {'e': e, 'productName': productName})

                self.anaconda.methodstr = self.anaconda.intf.methodstrRepoWindow(self.anaconda.methodstr or "cdrom:")

        self.doConfigSetup(root=self.anaconda.rootPath)
        self.conf.installonlypkgs = []

    def _switchCD(self, discnum):
        if os.access("%s/.discinfo" % self.tree, os.R_OK):
            f = open("%s/.discinfo" % self.tree)
            self._timestamp = f.readline().strip()
            f.close()

        dev = self.anaconda.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice)
        dev.format.mountpoint = self.tree

        # If self.currentMedia is None, then there shouldn't be anything
        # mounted.  Before going further, see if the correct disc is already
        # in the drive.  This saves a useless eject and insert if the user
        # has for some reason already put the disc in the drive.
        if self.currentMedia is None:
            try:
                dev.format.mount()

                if verifyMedia(self.tree, discnum, None):
                    self.currentMedia = discnum
                    return

                dev.format.unmount()
            except:
                pass
        else:
            unmountCD(dev, self.anaconda.intf.messageWindow)
            self.currentMedia = None

        dev.eject()

        while True:
            if self.anaconda.intf:
                self.anaconda.intf.beep()

            self.anaconda.intf.messageWindow(_("Change Disc"),
                _("Please insert %(productName)s disc %(discnum)d to continue.")
                % {'productName': productName, 'discnum': discnum})

            try:
                dev.format.mount()

                if verifyMedia(self.tree, discnum, self._timestamp):
                    self.currentMedia = discnum
                    break

                self.anaconda.intf.messageWindow(_("Wrong Disc"),
                        _("That's not the correct %s disc.")
                          % (productName,))

                dev.format.unmount()
                dev.eject()
            except:
                self.anaconda.intf.messageWindow(_("Error"),
                        _("Unable to access the disc."))

    def _switchImage(self, discnum):
        umountImage(self.tree, self.currentMedia)
        self.currentMedia = None

        # mountDirectory checks before doing anything, so it's safe to
        # call this repeatedly.
        mountDirectory(self.anaconda.methodstr,
                       self.anaconda.intf.messageWindow)

        self._discImages = mountImage(self.isodir, self.tree, discnum,
                                      self.anaconda.intf.messageWindow,
                                      discImages=self._discImages)
        self.currentMedia = discnum

    def configBaseURL(self):
        # We only have a methodstr if method= or repo= was passed to
        # anaconda.  No source for this base repo (the CD media, NFS,
        # whatever) is mounted yet since loader only mounts the source
        # for the stage2 image.  We need to set up the source mount
        # now.
        if flags.cmdline.has_key("preupgrade"):
            path = "/var/cache/yum/preupgrade"
            self.anaconda.methodstr = "hd::%s" % path 
            self._baseRepoURL = "file:///mnt/sysimage/%s" % path
        elif self.anaconda.methodstr:
            m = self.anaconda.methodstr

            if m.startswith("hd:"):
                if m.count(":") == 2:
                    (device, path) = m[3:].split(":")
                else:
                    (device, fstype, path) = m[3:].split(":")

                self.isodir = "/mnt/isodir/%s" % path

                # This takes care of mounting /mnt/isodir first.
                self._switchImage(1)
                self.mediagrabber = self.mediaHandler
            elif m.startswith("nfsiso:"):
                self.isodir = "/mnt/isodir"

                # Calling _switchImage takes care of mounting /mnt/isodir first.
                if not network.hasActiveNetDev():
                    if not self.anaconda.intf.enableNetwork():
                        self._baseRepoURL = None
                        return

                    urlgrabber.grabber.reset_curl_obj()

                self._switchImage(1)
                self.mediagrabber = self.mediaHandler
            elif m.startswith("http") or m.startswith("ftp:"):
                self._baseRepoURL = m
            elif m.startswith("nfs:"):
                if not network.hasActiveNetDev():
                    if not self.anaconda.intf.enableNetwork():
                        self._baseRepoURL = None

                    urlgrabber.grabber.reset_curl_obj()

                (opts, server, path) = iutil.parseNfsUrl(m)
                isys.mount(server+":"+path, self.tree, "nfs", options=opts)

                # This really should be fixed in loader instead but for now see
                # if there's images and if so go with this being an NFSISO
                # install instead.
                images = findIsoImages(self.tree, self.anaconda.intf.messageWindow)
                if images != {}:
                    isys.umount(self.tree, removeDir=False)
                    self.anaconda.methodstr = "nfsiso:%s" % m[4:]
                    self.configBaseURL()
                    return
            elif m.startswith("cdrom:"):
                self._switchCD(1)
                self.mediagrabber = self.mediaHandler
                self._baseRepoURL = "file://%s" % self.tree
        else:
            # No methodstr was given.  In order to find an installation source,
            # we should first check to see if there's a CD/DVD with packages
            # on it, and then default to the mirrorlist URL.  The user can
            # always change the repo with the repo editor later.
            cdr = scanForMedia(self.tree, self.anaconda.storage)
            if cdr:
                self.mediagrabber = self.mediaHandler
                self.anaconda.mediaDevice = cdr
                self.currentMedia = 1
                log.info("found installation media on %s" % cdr)
            else:
                # No CD with media on it and no repo=/method= parameter, so
                # default to using whatever's enabled in /etc/yum.repos.d/
                self._baseRepoURL = None

    def configBaseRepo(self, root='/'):
        # Create the "base" repo object, assuming there is one.  Otherwise we
        # just skip all this and use the defaults from /etc/yum.repos.d.
        if not self._baseRepoURL:
            return

        # add default repos
        anacondabaseurl = (self.anaconda.methodstr or
                           "cdrom:%s" % (self.anaconda.mediaDevice))
        anacondabasepaths = self.anaconda.instClass.getPackagePaths(anacondabaseurl)
        for (name, uri) in self.anaconda.instClass.getPackagePaths(self._baseRepoURL).items():
            rid = name.replace(" ", "")

            repo = AnacondaYumRepo("anaconda-%s-%s" % (rid, productStamp))
            repo.baseurl = uri
            repo.anacondaBaseURLs = anacondabasepaths[name]

            repo.name = name
            repo.cost = 100

            if self.anaconda.mediaDevice or self.isodir:
                repo.mediaid = getMediaId(self.tree)
                log.info("set mediaid of repo %s to: %s" % (rid, repo.mediaid))

            repo.enable()
            self.repos.add(repo)

    def mediaHandler(self, *args, **kwargs):
        mediaid = kwargs["mediaid"]
        discnum = kwargs["discnum"]
        relative = kwargs["relative"]

        # The package exists on media other than what's mounted right now.
        if discnum != self.currentMedia:
            log.info("switching from media #%s to #%s for %s" %
                     (self.currentMedia, discnum, relative))

            # Unmount any currently mounted ISO images and mount the one
            # containing the requested packages.
            if self.isodir:
                self._switchImage(discnum)
            else:
                self._switchCD(discnum)

        ug = URLGrabber(checkfunc=kwargs["checkfunc"])
        ug.urlgrab("%s/%s" % (self.tree, kwargs["relative"]), kwargs["local"],
                   text=kwargs["text"], range=kwargs["range"], copy_local=1)
        return kwargs["local"]

    # XXX: This is straight out of yum, but we need to override it here in
    # order to use our own repo class.
    def readRepoConfig(self, parser, section):
        '''Parse an INI file section for a repository.

        @param parser: ConfParser or similar to read INI file values from.
        @param section: INI file section to read.
        @return: YumRepository instance.
        '''
        repo = AnacondaYumRepo(section)
        repo.populate(parser, section, self.conf)

        # Ensure that the repo name is set
        if not repo.name:
            repo.name = section
            self.logger.error(_('Repository %r is missing name in configuration, '
                    'using id') % section)

        # Set attributes not from the config file
        repo.yumvar.update(self.conf.yumvar)
        repo.cfg = parser

        if "-source" in repo.id or "-debuginfo" in repo.id:
            name = repo.name
            del(repo)
            raise RepoError, "Repo %s contains -source or -debuginfo, excluding" % name

        # this is a little hard-coded, but it's effective
        if not BETANAG and ("rawhide" in repo.id or "development" in repo.id):
            name = repo.name
            del(repo)
            raise RepoError, "Excluding devel repo %s for non-devel anaconda" % name

        if BETANAG and not repo.enabled:
            name = repo.name
            del(repo)
            raise RepoError, "Excluding disabled repo %s for prerelease" % name

        # If repo=/method= was passed in, we want to default these extra
        # repos to off.
        if self._baseRepoURL:
            repo.enabled = False

        return repo

    # We need to make sure $releasever gets set up before .repo files are
    # read.  Since there's no redhat-release package in /mnt/sysimage (and
    # won't be for quite a while), we need to do our own substutition.
    def _getReleasever(self):
        from ConfigParser import ConfigParser
        c = ConfigParser()

        try:
            if os.access("%s/.treeinfo" % self.anaconda.methodstr, os.R_OK):
                ConfigParser.read(c, "%s/.treeinfo" % self.anaconda.methodstr)
            else:
                ug = URLGrabber()
                ug.urlgrab("%s/.treeinfo" % self.anaconda.methodstr,
                           "/tmp/.treeinfo", copy_local=1)
                ConfigParser.read(c, "/tmp/.treeinfo")

            return c.get("general", "version")
        except:
            return productVersion

    # Override this method so yum doesn't nuke our existing logging config.
    def doLoggingSetup(self, *args, **kwargs):

        import yum.logginglevels

        file_handler = logging.FileHandler("/tmp/yum.log")
        file_formatter = logging.Formatter("[%(asctime)s] %(levelname)-8s: %(message)s")
        file_handler.setFormatter(file_formatter)

        tty3_handler = logging.FileHandler("/dev/tty3")
        tty3_formatter = logging.Formatter(anaconda_log.TTY_FORMAT,
                                           anaconda_log.DATE_FORMAT)
        tty3_handler.setFormatter(tty3_formatter)

        verbose = logging.getLogger("yum.verbose")
        verbose.setLevel(logging.DEBUG)
        verbose.propagate = False
        verbose.addHandler(file_handler)

        logger = logging.getLogger("yum")
        logger.propagate = False
        logger.setLevel(yum.logginglevels.INFO_2)
        logger.addHandler(file_handler)
        anaconda_log.autoSetLevel(tty3_handler, True)
        tty3_handler.setLevel(anaconda_log.logger.tty_loglevel)
        logger.addHandler(tty3_handler)

        # XXX filelogger is set in setFileLog - do we or user want it?
        filelogger = logging.getLogger("yum.filelogging")
        filelogger.setLevel(logging.INFO)
        filelogger.propagate = False


    def doConfigSetup(self, fn='/tmp/anaconda-yum.conf', root='/'):
        if hasattr(self, "preconf"):
            self.preconf.fn = fn
            self.preconf.root = root
            self.preconf.releasever = self._getReleasever()
            self.preconf.enabled_plugins = ["whiteout", "blacklist"]
            YumSorter._getConfig(self)
        else:
            YumSorter._getConfig(self, fn=fn, root=root,
                                 enabled_plugins=["whiteout", "blacklist"])
        self.configBaseRepo(root=root)

        extraRepos = []

        ddArch = os.uname()[4]

        #Add the Driver disc repos to Yum
        for d in glob.glob(DD_RPMS):
            dirname = os.path.basename(d)
            rid = "anaconda-%s" % dirname

            repo = AnacondaYumRepo(rid)
            repo.baseurl = [ "file:///%s" % d ]
            repo.name = "Driver Disk %s" % dirname.split("-")[1]
            repo.enable()
            extraRepos.append(repo)

        if self.anaconda.ksdata:
            # This is the same pattern as from loader/urls.c:splitProxyParam.
            pattern = re.compile("([[:alpha:]]+://)?(([[:alnum:]]+)(:[^:@]+)?@)?([^:]+)(:[[:digit:]]+)?(/.*)?")

            for ksrepo in self.anaconda.ksdata.repo.repoList:
                anacondaBaseURLs = [ksrepo.baseurl]

                # yum doesn't understand nfs:// and doesn't want to.  We need
                # to first do the mount, then translate it into a file:// that
                # yum does understand.
                # "nfs:" and "nfs://" prefixes are accepted in ks repo --baseurl
                if ksrepo.baseurl and ksrepo.baseurl.startswith("nfs:"):
                    if not network.hasActiveNetDev() and not self.anaconda.intf.enableNetwork():
                        self.anaconda.intf.messageWindow(_("No Network Available"),
                            _("Some of your software repositories require "
                              "networking, but there was an error enabling the "
                              "network on your system."),
                            type="custom", custom_icon="error",
                            custom_buttons=[_("_Exit installer")])
                        sys.exit(1)

                    urlgrabber.grabber.reset_curl_obj()

                    dest = tempfile.mkdtemp("", ksrepo.name.replace(" ", ""), "/mnt")

                    # handle "nfs://" prefix
                    if ksrepo.baseurl[4:6] == '//':
                        ksrepo.baseurl = ksrepo.baseurl.replace('//', '', 1)
                        anacondaBaseURLs = [ksrepo.baseurl]
                    try:
                        isys.mount(ksrepo.baseurl[4:], dest, "nfs")
                    except Exception as e:
                        log.error("error mounting NFS repo: %s" % e)

                    ksrepo.baseurl = "file://%s" % dest

                repo = AnacondaYumRepo(ksrepo.name)
                repo.mirrorlist = ksrepo.mirrorlist
                repo.name = ksrepo.name

                if not ksrepo.baseurl:
                    repo.baseurl = []
                else:
                    repo.baseurl = [ ksrepo.baseurl ]
                repo.anacondaBaseURLs = anacondaBaseURLs

                if ksrepo.cost:
                    repo.cost = ksrepo.cost

                if ksrepo.excludepkgs:
                    repo.exclude = ksrepo.excludepkgs

                if ksrepo.includepkgs:
                    repo.include = ksrepo.includepkgs

                if ksrepo.proxy:
                    m = pattern.match(ksrepo.proxy)

                    if m and m.group(5):
                        # If both a host and port was found, just paste them
                        # together using the colon at the beginning of the port
                        # match as a separator.  Otherwise, just use the host.
                        if m.group(6):
                            repo.proxy = m.group(5) + m.group(6)
                        else:
                            repo.proxy = m.group(5)

                        # yum also requires a protocol.  If none was given,
                        # default to http.
                        if m.group(1):
                            repo.proxy = m.group(1) + repo.proxy
                        else:
                            repo.proxy = "http://" + repo.proxy

                    if m and m.group(3):
                        repo.proxy_username = m.group(3)

                    if m and m.group(4):
                        # Skip the leading colon.
                        repo.proxy_password = m.group(4)[1:]

                repo.enable()
                extraRepos.append(repo)

        for repo in extraRepos:
            try:
                self.repos.add(repo)
                log.info("added repository %s with URL %s" % (repo.name, repo.mirrorlist or repo.baseurl))
            except:
                log.warning("ignoring duplicate repository %s with URL %s" % (repo.name, repo.mirrorlist or repo.baseurl))

        self.repos.setCacheDir(self.conf.cachedir)

        if os.path.exists("%s/boot/upgrade/install.img" % self.anaconda.rootPath):
            log.info("REMOVING stage2 image from %s /boot/upgrade" % self.anaconda.rootPath )
            try:
                os.unlink("%s/boot/upgrade/install.img" % self.anaconda.rootPath)
            except:
                log.warning("failed to clean /boot/upgrade")

    def downloadHeader(self, po):
        while True:
            # retrying version of download header
            try:
                YumSorter.downloadHeader(self, po)
                break
            except yum.Errors.NoMoreMirrorsRepoError:
                self._handleFailure(po)
            except IOError:
                self._handleFailure(po)
            except yum.Errors.RepoError, e:
                continue

    def _handleFailure(self, package):
        if not self.isodir and self.currentMedia:
            buttons = [_("Re_boot"), _("_Eject")]
        else:
            buttons = [_("Re_boot"), _("_Retry")]

        pkgFile = to_unicode(os.path.basename(package.remote_path))
        rc = self.anaconda.intf.messageWindow(_("Error"),
                   _("The file %s cannot be opened.  This is due to a missing "
                     "file, a corrupt package or corrupt media.  Please "
                     "verify your installation source.\n\n"
                     "If you exit, your system will be left in an inconsistent "
                     "state that will likely require reinstallation.\n\n") %
                                              (pkgFile,),
                                    type="custom", custom_icon="error",
                                    custom_buttons=buttons)

        if rc == 0:
            sys.exit(0)
        else:
            if os.path.exists(package.localPkg()):
                os.unlink(package.localPkg())

            if not self.isodir and self.currentMedia:
                self._switchCD(self.currentMedia)
            else:
                return

    def mirrorFailureCB (self, obj, *args, **kwargs):
        # This gets called when a mirror fails, but it cannot know whether
        # or not there are other mirrors left to try, since it cannot know
        # which mirror we were on when we started this particular download. 
        # Whenever we have run out of mirrors the grabber's get/open/retrieve
        # method will raise a URLGrabError exception with errno 256.
        grab = self.repos.getRepo(kwargs["repo"]).grab
        log.warning("Failed to get %s from mirror %d/%d, "
                    "or downloaded file is corrupt" % (obj.url, grab._next + 1,
                                                       len(grab.mirrors)))

        if self.currentMedia:
            dev = self.anaconda.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice)
            dev.format.mountpoint = self.tree
            unmountCD(dev, self.anaconda.intf.messageWindow)
            self.currentMedia = None

    def urlgrabberFailureCB (self, obj, *args, **kwargs):
        if hasattr(obj, "exception"):
            log.warning("Try %s/%s for %s failed: %s" % (obj.tries, obj.retry, obj.url, obj.exception))
        else:
            log.warning("Try %s/%s for %s failed" % (obj.tries, obj.retry, obj.url))

        if obj.tries == obj.retry:
            return

        delay = 0.25*(2**(obj.tries-1))
        if delay > 1:
            w = self.anaconda.intf.waitWindow(_("Retrying"), _("Retrying download."))
            time.sleep(delay)
            w.pop()
        else:
            time.sleep(delay)

    def getDownloadPkgs(self):
        downloadpkgs = []
        totalSize = 0
        totalFiles = 0
        for txmbr in self.tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES):
            if txmbr.po:
                totalSize += int(txmbr.po.returnSimple("installedsize")) / 1024
                for filetype in txmbr.po.returnFileTypes():
                    totalFiles += len(txmbr.po.returnFileEntries(ftype=filetype))
                downloadpkgs.append(txmbr.po)

        return (downloadpkgs, totalSize, totalFiles)

    def setColor(self):
        if rpmUtils.arch.isMultiLibArch():
            self.ts.ts.setColor(3)

    def run(self, instLog, cb, intf, id):
        def mediasort(a, b):
            # sort so that first CD comes first, etc.  -99 is a magic number
            # to tell us that the cd should be last
            if a == -99:
                return 1
            elif b == -99:
                return -1
            if a < b:
                return -1
            elif a > b:
                return 1
            return 0

        self.initActionTs()
        if self.anaconda.upgrade:
            self.ts.ts.setProbFilter(~rpm.RPMPROB_FILTER_DISKSPACE)
        self.setColor()

        # If we don't have any required media assume single disc
        if self.tsInfo.reqmedia == {}:
            self.tsInfo.reqmedia[0] = None
        mkeys = self.tsInfo.reqmedia.keys()
        mkeys.sort(mediasort)

        stage2img = "%s/images/install.img" % self.tree
        if os.path.exists(stage2img):
            if self.anaconda.backend.mountInstallImage(self.anaconda, stage2img):
                self.anaconda.storage.umountFilesystems()
                return DISPATCH_BACK

        for i in mkeys:
            self.tsInfo.curmedia = i
            if i > 0:
                pkgtup = self.tsInfo.reqmedia[i][0]

            try:
                self.dsCallback = DownloadHeaderProgress(intf, self)
                self.populateTs(keepold=0)
                self.dsCallback.pop()
                self.dsCallback = None
            except RepoError, e:
                msg = _("There was an error running your transaction for "
                        "the following reason: %s\n") % str(e)

                if self.anaconda.upgrade:
                    rc = intf.messageWindow(_("Error"), msg, type="custom",
                                            custom_icon="error",
                                            custom_buttons=[_("_Exit installer")])
                    sys.exit(1)
                else:
                    rc = intf.messageWindow(_("Error"), msg,
                            type="custom", custom_icon="error",
                            custom_buttons=[_("_Back"), _("_Exit installer")])

                if rc == 1:
                    sys.exit(1)
                else:
                    self.tsInfo.curmedia = None
                    return DISPATCH_BACK

            self.ts.check()
            self.ts.order()

            if self._run(instLog, cb, intf) == DISPATCH_BACK:
                self.tsInfo.curmedia = None
                return DISPATCH_BACK

            self.ts.close()

    def _run(self, instLog, cb, intf):
        # set log fd.  FIXME: this is ugly.  see changelog entry from 2005-09-13
        self.ts.ts.scriptFd = instLog.fileno()
        rpm.setLogFile(instLog)

        uniqueProbs = {}
        spaceneeded = {}
        spaceprob = ""
        fileConflicts = []
        fileprob = ""

        try:
            self.runTransaction(cb=cb)
        except YumBaseError, probs:
            # FIXME: we need to actually look at these problems...
            probTypes = { rpm.RPMPROB_NEW_FILE_CONFLICT : _('file conflicts'),
                          rpm.RPMPROB_FILE_CONFLICT : _('file conflicts'),
                          rpm.RPMPROB_OLDPACKAGE: _('older package(s)'),
                          rpm.RPMPROB_DISKSPACE: _('insufficient disk space'),
                          rpm.RPMPROB_DISKNODES: _('insufficient disk inodes'),
                          rpm.RPMPROB_CONFLICT: _('package conflicts'),
                          rpm.RPMPROB_PKG_INSTALLED: _('package already installed'),
                          rpm.RPMPROB_REQUIRES: _('required package'),
                          rpm.RPMPROB_BADARCH: _('package for incorrect arch'),
                          rpm.RPMPROB_BADOS: _('package for incorrect os'),
            }

            for (descr, (ty, mount, need)) in probs.value: # FIXME: probs.value???
                log.error("%s: %s" %(probTypes[ty], descr))
                if not uniqueProbs.has_key(ty) and probTypes.has_key(ty):
                    uniqueProbs[ty] = probTypes[ty]

                if ty == rpm.RPMPROB_DISKSPACE:
                    spaceneeded[mount] = need
                elif ty in [rpm.RPMPROB_NEW_FILE_CONFLICT, rpm.RPMPROB_FILE_CONFLICT]:
                    fileConflicts.append(descr)

            if spaceneeded:
                spaceprob = _("You need more space on the following "
                              "file systems:\n")

                for (mount, need) in spaceneeded.items():
                    log.info("(%s, %s)" %(mount, need))

                    if mount.startswith("/mnt/sysimage/"):
                        mount.replace("/mnt/sysimage", "")
                    elif mount.startswith("/mnt/sysimage"):
                        mount = "/" + mount.replace("/mnt/sysimage", "")

                    spaceprob += "%d M on %s\n" % (need / (1024*1024), mount)
            elif fileConflicts:
                fileprob = _("There were file conflicts when checking the "
                             "packages to be installed:\n%s\n") % ("\n".join(fileConflicts),)

            msg = _("There was an error running your transaction for "
                    "the following reason(s): %s.\n") % ', '.join(uniqueProbs.values())

            spaceprob = to_unicode(spaceprob)
            fileprob = to_unicode(fileprob)

            if len(self.anaconda.backend.getRequiredMedia()) > 1 or self.anaconda.upgrade:
                intf.detailedMessageWindow(_("Error Running Transaction"),
                   msg, spaceprob + "\n" + fileprob, type="custom",
                   custom_icon="error", custom_buttons=[_("_Exit installer")])
                sys.exit(1)
            else:
                rc = intf.detailedMessageWindow(_("Error Running Transaction"),
                        msg, spaceprob + "\n" + fileprob, type="custom",
                        custom_icon="error",
                        custom_buttons=[_("_Back"), _("_Exit installer")])

            if rc == 1:
                sys.exit(1)
            else:
                self._undoDepInstalls()
                return DISPATCH_BACK

    def doMacros(self):
        for (key, val) in self.macros.items():
            rpm.addMacro(key, val)

    def simpleDBInstalled(self, name, arch=None):
        # FIXME: doing this directly instead of using self.rpmdb.installed()
        # speeds things up by 400%
        mi = self.ts.ts.dbMatch('name', name)
        if mi.count() == 0:
            return False
        if arch is None:
            return True
        if arch in map(lambda h: h['arch'], mi):
            return True
        return False

    def isPackageInstalled(self, name = None, epoch = None, version = None,
                           release = None, arch = None, po = None):
        # FIXME: this sucks.  we should probably suck it into yum proper
        # but it'll need a bit of cleanup first.
        if po is not None:
            (name, epoch, version, release, arch) = po.returnNevraTuple()

        installed = False
        if name and not (epoch or version or release or arch):
            installed = self.simpleDBInstalled(name)
        elif self.rpmdb.installed(name = name, epoch = epoch, ver = version,
                                rel = release, arch = arch):
            installed = True

        lst = self.tsInfo.matchNaevr(name = name, epoch = epoch,
                                     ver = version, rel = release,
                                     arch = arch)
        for txmbr in lst:
            if txmbr.output_state in TS_INSTALL_STATES:
                return True
        if installed and len(lst) > 0:
            # if we get here, then it was installed, but it's in the tsInfo
            # for an erase or obsoleted --> not going to be installed at end
            return False
        return installed

    def isGroupInstalled(self, grp):
        if grp.selected:
            return True
        elif grp.installed and not grp.toremove:
            return True
        return False

    def _pkgExists(self, pkg):
        """Whether or not a given package exists in our universe."""
        try:
            pkgs = self.pkgSack.returnNewestByName(pkg)
            return True
        except yum.Errors.PackageSackError:
            pass
        try:
            pkgs = self.rpmdb.returnNewestByName(pkg)
            return True
        except (IndexError, yum.Errors.PackageSackError):
            pass
        return False

    def _groupHasPackages(self, grp):
        # this checks to see if the given group has any packages available
        # (ie, already installed or in the sack of available packages)
        # so that we don't show empty groups.  also, if there are mandatory
        # packages and we have none of them, don't show
        for pkg in grp.mandatory_packages.keys():
            if self._pkgExists(pkg):
                return True
        if len(grp.mandatory_packages) > 0:
            return False
        for pkg in grp.default_packages.keys() + grp.optional_packages.keys():
            if self._pkgExists(pkg):
                return True
        return False

class YumBackend(AnacondaBackend):
    def __init__ (self, anaconda):
        AnacondaBackend.__init__(self, anaconda)
        self.supportsPackageSelection = True

        buf = """
[main]
installroot=%s
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
logfile=/tmp/yum.log
metadata_expire=0
obsoletes=True
pluginpath=/usr/lib/yum-plugins,/tmp/updates/yum-plugins
pluginconfpath=/etc/yum/pluginconf.d,/tmp/updates/pluginconf.d
plugins=1
reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anaconda.repos.d
""" % (anaconda.rootPath)

        if anaconda.proxy:
            buf += "proxy=%s\n" % anaconda.proxy

            if anaconda.proxyUsername:
                buf += "proxy_username=%s\n" % anaconda.proxyUsername

            if anaconda.proxyPassword:
                buf += "proxy_password=%s\n" % anaconda.proxyPassword

        fd = open("/tmp/anaconda-yum.conf", "w")
        fd.write(buf)
        fd.close()

    def complete(self, anaconda):
        if not anaconda.mediaDevice and os.path.ismount(self.ayum.tree):
            isys.umount(self.ayum.tree)

        anaconda.backend.removeInstallImage()

        # clean up rpmdb locks so that kickstart %post scripts aren't
        # unhappy (#496961)
        iutil.resetRpmDb(anaconda.rootPath)

    def doBackendSetup(self, anaconda):
        if anaconda.dir == DISPATCH_BACK:
            return DISPATCH_BACK

        if anaconda.upgrade:
           # FIXME: make sure that the rpmdb doesn't have stale locks :/
           iutil.resetRpmDb(anaconda.rootPath)

        anaconda.backend.freetmp(anaconda)
        self.ayum = AnacondaYum(anaconda)
        self.ayum.setup()

        self.ayum.doMacros()

        # If any enabled repositories require networking, go ahead and bring
        # it up now.  No need to have people wait for the timeout when we
        # know this in advance.
        for repo in self.ayum.repos.listEnabled():
            if repo.needsNetwork() and not network.hasActiveNetDev():
                if not anaconda.intf.enableNetwork():
                    anaconda.intf.messageWindow(_("No Network Available"),
                        _("Some of your software repositories require "
                          "networking, but there was an error enabling the "
                          "network on your system."),
                        type="custom", custom_icon="error",
                        custom_buttons=[_("_Exit installer")])
                    sys.exit(1)

                urlgrabber.grabber.reset_curl_obj()
                break

        self.doRepoSetup(anaconda)
        self.doSackSetup(anaconda)
        self.doGroupSetup(anaconda)

        self.ayum.doMacros()

    def doGroupSetup(self, anaconda):
        while True:
            try:
                self.ayum.doGroupSetup()
            except (GroupsError, NoSuchGroup, RepoError), e:
                buttons = [_("_Exit installer"), _("_Retry")]
            else:
                break # success

            rc = anaconda.intf.messageWindow(_("Error"),
                                        _("Unable to read group information "
                                          "from repositories.  This is "
                                          "a problem with the generation "
                                          "of your install tree."),
                                        type="custom", custom_icon="error",
                                        custom_buttons = buttons)
            if rc == 0:
                sys.exit(0)
            else:
                self.ayum._setGroups(None)
                continue

    def doRepoSetup(self, anaconda, thisrepo = None, fatalerrors = True):
        self.__withFuncDo(anaconda, lambda r: self.ayum.doRepoSetup(thisrepo=r.id),
                          thisrepo=thisrepo, fatalerrors=fatalerrors)

    def doSackSetup(self, anaconda, thisrepo = None, fatalerrors = True):
        self.__withFuncDo(anaconda, lambda r: self.ayum.doSackSetup(thisrepo=r.id),
                          thisrepo=thisrepo, fatalerrors=fatalerrors)

    def __withFuncDo(self, anaconda, fn, thisrepo=None, fatalerrors=True):
        # Don't do this if we're being called as a dispatcher step (instead
        # of being called when a repo is added via the UI) and we're going
        # back.
        if thisrepo is None and anaconda.dir == DISPATCH_BACK:
            return

        # We want to call the function one repo at a time so we have some
        # concept of which repo didn't set up correctly.
        if thisrepo is not None:
            repos = [self.ayum.repos.getRepo(thisrepo)]
        else:
            repos = self.ayum.repos.listEnabled()

        for repo in repos:
            if repo.name is None:
                txt = _("Retrieving installation information.")
            else:
                txt = _("Retrieving installation information for %s.")%(repo.name)

            waitwin = anaconda.intf.waitWindow(_("Installation Progress"), txt)

            while True:
                try:
                    fn(repo)
                    waitwin.pop()
                except RepoError, e:
                    waitwin.pop()
                    buttons = [_("_Exit installer"), _("Edit"), _("_Retry")]
                else:
                    break # success

                if anaconda.ksdata:
                    buttons.append(_("_Continue"))

                if not fatalerrors:
                    raise RepoError, e

                rc = anaconda.intf.messageWindow(_("Error"),
                                   _("Unable to read package metadata. This may be "
                                     "due to a missing repodata directory.  Please "
                                     "ensure that your install tree has been "
                                     "correctly generated.\n\n%s" % e),
                                     type="custom", custom_icon="error",
                                     custom_buttons=buttons)
                if rc == 0:
                    # abort
                    sys.exit(0)
                elif rc == 1:
                    # edit
                    anaconda.intf.editRepoWindow(repo)
                    break
                elif rc == 2:
                    # retry, but only if button is present
                    continue
                else:
                    # continue, but only if button is present
                    self.ayum.repos.delete(repo.id)
                    break

            # if we're in kickstart the repo may have been deleted just above
            try:
                self.ayum.repos.getRepo(repo.id)
            except RepoError:
                log.debug("repo %s has been removed" % (repo.id,))
                continue

            repo.setFailureObj(self.ayum.urlgrabberFailureCB)
            repo.setMirrorFailureObj((self.ayum.mirrorFailureCB, (),
                                     {"repo": repo.id}))

        self.ayum.repos.callback = None

    def getDefaultGroups(self, anaconda):
        langs = anaconda.instLanguage.getCurrentLangSearchList()
        rc = map(lambda x: x.groupid,
                 filter(lambda x: x.default, self.ayum.comps.groups))
        for g in self.ayum.comps.groups:
            if g.langonly in langs:
                rc.append(g.groupid)
        return rc

    def resetPackageSelections(self):
        """Reset the package selection to an empty state."""
        for txmbr in self.ayum.tsInfo:
            self.ayum.tsInfo.remove(txmbr.pkgtup)
        self.ayum.tsInfo.conditionals.clear()
        for grp in self.ayum.comps.groups:
            grp.selected = False

    def selectModulePackages(self, anaconda, kernelPkgName):
        (base, sep, ext) = kernelPkgName.partition("-")

        moduleProvides = []

        for (path, name) in anaconda.extraModules:
            if ext != "":
                moduleProvides.append("dud-%s-%s" % (name, ext))
            else:
                moduleProvides.append("dud-%s" % name)

        #We need to install the packages which contain modules from DriverDiscs
        for modPath in isys.modulesWithPaths():
            if modPath.startswith(DD_EXTRACTED):
                moduleProvides.append(modPath[len(DD_EXTRACTED):])
            else:
                continue

        for module in moduleProvides:
            pkgs = self.ayum.returnPackagesByDep(module)

            if not pkgs:
                log.warning("Didn't find any package providing %s" % module)

            for pkg in pkgs:
                log.info("selecting package %s for %s" % (pkg.name, module))
                self.ayum.install(po=pkg)

    def selectBestKernel(self, anaconda):
        """Find the best kernel package which is available and select it."""

        def getBestKernelByArch(pkgname, ayum):
            """Convenience func to find the best arch of a kernel by name"""
            try:
                pkgs = ayum.pkgSack.returnNewestByName(pkgname)
            except yum.Errors.PackageSackError:
                return None

            pkgs = self.ayum.bestPackagesFromList(pkgs)
            if len(pkgs) == 0:
                return None
            return pkgs[0]

        def selectKernel(pkgname):
            try:
                pkg = getBestKernelByArch(pkgname, self.ayum)
            except PackageSackError:
                log.debug("no %s package" % pkgname)
                return False

            if not pkg:
                return False

            log.info("selected %s package for kernel" % pkg.name)
            self.ayum.install(po=pkg)
            self.selectModulePackages(anaconda, pkg.name)

            if len(self.ayum.tsInfo.matchNaevr(name="gcc")) > 0:
                log.debug("selecting %s-devel" % pkg.name)
                self.selectPackage("%s-devel.%s" % (pkg.name, pkg.arch))

            return True

        foundkernel = False

        if not foundkernel and isys.isPaeAvailable():
            if selectKernel("kernel-PAE"):
                foundkernel = True

        if not foundkernel:
            selectKernel("kernel")

    def selectFSPackages(self, storage):
        for device in storage.fsset.devices:
            # this takes care of device and filesystem packages
            map(self.selectPackage, device.packages)

    # anaconda requires several programs on the installed system to complete
    # installation, but we have no guarantees that some of these will be
    # installed (they could have been removed in kickstart).  So we'll force
    # it.
    def selectAnacondaNeeds(self):
        for pkg in ['authconfig', 'chkconfig', 'system-config-firewall-base']:
            self.selectPackage(pkg)

    def doPostSelection(self, anaconda):
        # Only solve dependencies on the way through the installer, not the way back.
        if anaconda.dir == DISPATCH_BACK:
            return

        dscb = YumDepSolveProgress(anaconda.intf, self.ayum)
        self.ayum.dsCallback = dscb

        # do some sanity checks for kernel and bootloader
        if not anaconda.upgrade:
            # New installs only - upgrades will already have all this stuff.
            self.selectBestKernel(anaconda)
            map(self.selectPackage, anaconda.platform.packages)
            self.selectFSPackages(anaconda.storage)
            self.selectAnacondaNeeds()
        else:
            self.ayum.update()

        while True:
            try:
                (code, msgs) = self.ayum.buildTransaction()

                # If %packages --ignoremissing was given, don't bother
                # prompting for missing dependencies.
                if anaconda.ksdata and anaconda.ksdata.packages.handleMissing == KS_MISSING_IGNORE:
                    break

                if code == 1 and not anaconda.upgrade:
                    # resolveDeps returns 0 if empty transaction, 1 if error,
                    # 2 if success
                    depprob = "\n".join(msgs)

                    rc = anaconda.intf.detailedMessageWindow(_("Warning"),
                            _("Some of the packages you have selected for "
                              "install are missing dependencies.  You can "
                              "exit the installation, go back and change "
                              "your package selections, or continue "
                              "installing these packages without their "
                              "dependencies.  If you continue, these packages "
                              "may not work correctly due to missing components."),
                            depprob + "\n", type="custom", custom_icon="error",
                            custom_buttons=[_("_Exit installer"), _("_Back"),
                                            _("_Continue")])
                    dscb.pop()

                    if rc == 0:
                        sys.exit(1)
                    elif rc == 1:
                        self.ayum._undoDepInstalls()
                        return DISPATCH_BACK

                break
            except RepoError, e:
                # FIXME: would be nice to be able to recover here
                rc = anaconda.intf.messageWindow(_("Error"),
                               _("Unable to read package metadata. This may be "
                                 "due to a missing repodata directory.  Please "
                                 "ensure that your install tree has been "
                                 "correctly generated.\n\n%s" % e),
                                 type="custom", custom_icon="error",
                                 custom_buttons=[_("_Exit installer"), _("_Retry")])
                dscb.pop()

                if rc == 0:
                    sys.exit(0)
                else:
                    continue
            else:
                break

        (self.dlpkgs, self.totalSize, self.totalFiles)  = self.ayum.getDownloadPkgs()

        if not anaconda.upgrade:
            largePart = anaconda.storage.mountpoints.get("/usr", anaconda.storage.rootDevice)

            if largePart and largePart.size < self.totalSize / 1024:
                rc = anaconda.intf.messageWindow(_("Error"),
                                        _("Your selected packages require %d MB "
                                          "of free space for installation, but "
                                          "you do not have enough available.  "
                                          "You can change your selections or "
                                          "exit the installer." % (self.totalSize / 1024)),
                                        type="custom", custom_icon="error",
                                        custom_buttons=[_("_Back"), _("_Exit installer")])

                dscb.pop()

                if rc == 1:
                    sys.exit(1)
                else:
                    self.ayum._undoDepInstalls()
                    return DISPATCH_BACK

        dscb.pop()

        if anaconda.mediaDevice and not anaconda.ksdata:
           rc = presentRequiredMediaMessage(anaconda)
           if rc == 0:
               rc2 = anaconda.intf.messageWindow(_("Reboot?"),
                                       _("The system will be rebooted now."),
                                       type="custom", custom_icon="warning",
                                       custom_buttons=[_("_Back"), _("_Reboot")])
               if rc2 == 1:
                   sys.exit(0)
               else:
                   return DISPATCH_BACK
           elif rc == 1: # they asked to go back
               return DISPATCH_BACK

        self.ayum.dsCallback = None

    def doPreInstall(self, anaconda):
        if anaconda.dir == DISPATCH_BACK:
            for d in ("/selinux", "/dev", "/proc/bus/usb"):
                try:
                    isys.umount(anaconda.rootPath + d, removeDir = False)
                except Exception, e:
                    log.error("unable to unmount %s: %s" %(d, e))
            return

        if anaconda.upgrade:
            # An old mtab can cause confusion (esp if loop devices are
            # in it).  Be extra special careful and delete any mtab first,
            # in case the user has done something funny like make it into
            # a symlink.
            if os.access(anaconda.rootPath + "/etc/mtab", os.F_OK):
                os.remove(anaconda.rootPath + "/etc/mtab")

            f = open(anaconda.rootPath + "/etc/mtab", "w+")
            f.close()

            # we really started writing modprobe.conf out before things were
            # all completely ready.  so now we need to nuke old modprobe.conf's
            # if you're upgrading from a 2.4 dist so that we can get the
            # transition right
            if (os.path.exists(anaconda.rootPath + "/etc/modules.conf") and
                os.path.exists(anaconda.rootPath + "/etc/modprobe.conf") and
                not os.path.exists(anaconda.rootPath + "/etc/modprobe.conf.anacbak")):
                log.info("renaming old modprobe.conf -> modprobe.conf.anacbak")
                os.rename(anaconda.rootPath + "/etc/modprobe.conf",
                          anaconda.rootPath + "/etc/modprobe.conf.anacbak")

        dirList = ['/var', '/var/lib', '/var/lib/rpm', '/tmp', '/dev', '/etc',
                   '/etc/sysconfig', '/etc/sysconfig/network-scripts',
                   '/etc/X11', '/root', '/var/tmp', '/etc/rpm', '/var/cache',
                   '/var/cache/yum', '/etc/modprobe.d']

        # If there are any protected partitions we want to mount, create their
        # mount points now.
        for protected in anaconda.storage.protectedDevices:
            if getattr(protected.format, "mountpoint", None):
                dirList.append(protected.format.mountpoint)

        for i in dirList:
            try:
                os.mkdir(anaconda.rootPath + i)
            except os.error, (errno, msg):
                pass
#            log.error("Error making directory %s: %s" % (i, msg))

        self.initLog(anaconda.rootPath)

        try:
            # FIXME: making the /var/lib/rpm symlink here is a hack to
            # workaround db->close() errors from rpm
            iutil.mkdirChain("/var/lib")
            for path in ("/var/tmp", "/var/lib/rpm"):
                if os.path.exists(path) and not os.path.islink(path):
                    shutil.rmtree(path)
                if not os.path.islink(path):
                    os.symlink("%s/%s" %(anaconda.rootPath, path), "%s" %(path,))
                else:
                    log.warning("%s already exists as a symlink to %s" %(path, os.readlink(path),))
        except Exception, e:
            # how this could happen isn't entirely clear; log it in case
            # it does and causes problems later
            log.error("error creating symlink, continuing anyway: %s" %(e,))

        # SELinux hackery (#121369)
        if flags.selinux:
            try:
                os.mkdir(anaconda.rootPath + "/selinux")
            except Exception, e:
                pass
            try:
                isys.mount("/selinux", anaconda.rootPath + "/selinux", "selinuxfs")
            except Exception, e:
                log.error("error mounting selinuxfs: %s" %(e,))

        # For usbfs
        try:
            isys.mount("/proc/bus/usb", anaconda.rootPath + "/proc/bus/usb", "usbfs")
        except Exception, e:
            log.error("error mounting usbfs: %s" %(e,))

        # write out the fstab
        if not anaconda.upgrade:
            anaconda.storage.fsset.write(anaconda.rootPath)
            if os.access("/etc/modprobe.d/anaconda.conf", os.R_OK):
                shutil.copyfile("/etc/modprobe.d/anaconda.conf", 
                                anaconda.rootPath + "/etc/modprobe.d/anaconda.conf")
            anaconda.network.write(instPath=anaconda.rootPath, anaconda=anaconda)
            anaconda.storage.write(anaconda.rootPath)
            if not anaconda.isHeadless:
                anaconda.keyboard.write(anaconda.rootPath)

        # make a /etc/mtab so mkinitrd can handle certain hw (usb) correctly
        f = open(anaconda.rootPath + "/etc/mtab", "w+")
        f.write(anaconda.storage.mtab)
        f.close()

    def checkSupportedUpgrade(self, anaconda):
        if anaconda.dir == DISPATCH_BACK:
            return
        self._checkUpgradeVersion(anaconda)
        self._checkUpgradeArch(anaconda)

    def _checkUpgradeVersion(self, anaconda):
        # Figure out current version for upgrade nag and for determining weird
        # upgrade cases
        supportedUpgradeVersion = -1
        for pkgtup in self.ayum.rpmdb.whatProvides('redhat-release', None, None):
            n, a, e, v, r = pkgtup
            if supportedUpgradeVersion <= 0:
                val = rpmUtils.miscutils.compareEVR((None, '3', '1'),
                                                    (e, v,r))
                if val > 0:
                    supportedUpgradeVersion = 0
                else:
                    supportedUpgradeVersion = 1
                    break

        if "Red Hat Enterprise Linux" not in productName:
            supportedUpgradeVersion = 1

        if supportedUpgradeVersion == 0:
            rc = anaconda.intf.messageWindow(_("Warning"),
                                    _("You appear to be upgrading from a system "
                                      "which is too old to upgrade to this "
                                      "version of %s.  Are you sure you wish to "
                                      "continue the upgrade "
                                      "process?") %(productName,),
                                    type = "yesno")
            if rc == 0:
                iutil.resetRpmDb(anaconda.rootPath)
                sys.exit(0)

    def _checkUpgradeArch(self, anaconda):
        def compareArch(a, b):
            if re.match("i.86", a) and re.match("i.86", b):
                return True
            else:
                return a == b

        # get the arch of the initscripts package
        try:
            pkgs = self.ayum.pkgSack.returnNewestByName('initscripts')
        except yum.Errors.PackageSackError:
            log.info("no packages named initscripts")
            return None

        pkgs = self.ayum.bestPackagesFromList(pkgs)
        if len(pkgs) == 0:
            log.info("no best package")
            return
        myarch = pkgs[0].arch

        log.info("initscripts is arch: %s" %(myarch,))
        for po in self.ayum.rpmdb.getProvides('initscripts'):
            log.info("po.arch is arch: %s" %(po.arch,))
            if not compareArch(po.arch, myarch):
                rc = anaconda.intf.messageWindow(_("Warning"),
                         _("The arch of the release of %(productName)s you "
                           "are upgrading to appears to be %(myarch)s which "
                           "does not match your previously installed arch of "
                           "%(arch)s.  This is likely to not succeed.  Are "
                           "you sure you wish to continue the upgrade "
                           "process?")
                         % {'productName': productName,
                            'myarch': myarch,
                            'arch': po.arch},
                         type="yesno")
                if rc == 0:
                    iutil.resetRpmDb(anaconda.rootPath)
                    sys.exit(0)
                else:
                    log.warning("upgrade between possibly incompatible "
                                "arches %s -> %s" %(po.arch, myarch))
                    break

    def doInstall(self, anaconda):
        log.info("Preparing to install packages")

        if not anaconda.upgrade:
            rpm.addMacro("__dbi_htconfig",
                         "hash nofsync %{__dbi_other} %{__dbi_perms}")

        if anaconda.ksdata and anaconda.ksdata.packages.excludeDocs:
            rpm.addMacro("_excludedocs", "1")

        cb = AnacondaCallback(self.ayum, anaconda,
                              self.instLog, self.modeText)
        cb.setSizes(len(self.dlpkgs), self.totalSize, self.totalFiles)

        rc = self.ayum.run(self.instLog, cb, anaconda.intf, anaconda.id)

        if cb.initWindow is not None:
            cb.initWindow.pop()

        self.instLog.write("*** FINISHED INSTALLING PACKAGES ***")
        self.instLog.close ()

        anaconda.intf.setInstallProgressClass(None)

        if rc == DISPATCH_BACK:
            return DISPATCH_BACK

    def doPostInstall(self, anaconda):
        if anaconda.upgrade:
            w = anaconda.intf.waitWindow(_("Post Upgrade"),
                                    _("Performing post-upgrade configuration"))
        else:
            w = anaconda.intf.waitWindow(_("Post Installation"),
                                    _("Performing post-installation configuration"))

        packages.rpmSetupGraphicalSystem(anaconda)

        for repo in self.ayum.repos.listEnabled():
            repo.dirCleanup()

        # expire yum caches on upgrade
        if anaconda.upgrade and os.path.exists("%s/var/cache/yum" %(anaconda.rootPath,)):
            log.info("Expiring yum caches")
            try:
                iutil.execWithRedirect("yum", ["clean", "all"],
                                       stdout="/dev/tty5", stderr="/dev/tty5",
                                       root = anaconda.rootPath)
            except:
                pass

        # nuke preupgrade
        if flags.cmdline.has_key("preupgrade") and os.path.exists("%s/var/cache/yum/anaconda-upgrade" %(anaconda.rootPath,)):
            try:
                shutil.rmtree("%s/var/cache/yum/anaconda-upgrade" %(anaconda.rootPath,))
            except:
                pass

        # XXX: write proper lvm config

        AnacondaBackend.doPostInstall(self, anaconda)
        w.pop()

    def kernelVersionList(self, rootPath="/"):
        # FIXME: using rpm here is a little lame, but otherwise, we'd
        # be pulling in filelists
        return packages.rpmKernelVersionList(rootPath)

    def __getGroupId(self, group):
        """Get the groupid for the given name (english or translated)."""
        for g in self.ayum.comps.groups:
            if group == g.name:
                return g.groupid
            for trans in g.translated_name.values():
                if group == trans:
                    return g.groupid

    def isGroupSelected(self, group):
        try:
            grp = self.ayum.comps.return_group(group)
            if grp.selected: return True
        except yum.Errors.GroupsError, e:
            pass
        return False

    def selectGroup(self, group, *args):
        if not self.ayum.comps.has_group(group):
            log.debug("no such group %s" % group)
            raise NoSuchGroup, group

        types = ["mandatory"]

        if args:
            if args[0][0]:
                types.append("default")
            if args[0][1]:
                types.append("optional")
        else:
            types.append("default")

        try:
            mbrs = self.ayum.selectGroup(group, group_package_types=types)
            if len(mbrs) == 0 and self.isGroupSelected(group):
                return
        except yum.Errors.GroupsError, e:
            # try to find out if it's the name or translated name
            gid = self.__getGroupId(group)
            if gid is not None:
                mbrs = self.ayum.selectGroup(gid, group_package_types=types)
                if len(mbrs) == 0 and self.isGroupSelected(gid):
                    return
            else:
                log.debug("no such group %s" %(group,))
                raise NoSuchGroup, group

    def deselectGroup(self, group, *args):
        try:
            self.ayum.deselectGroup(group)
        except yum.Errors.GroupsError, e:
            # try to find out if it's the name or translated name
            gid = self.__getGroupId(group)
            if gid is not None:
                self.ayum.deselectGroup(gid)
            else:
                log.debug("no such group %s" %(group,))

    def selectPackage(self, pkg, *args):
        if self.ayum.tsInfo.matchNaevr(name=pkg):
            return 0

        try:
            mbrs = self.ayum.install(pattern=pkg)
            return len(mbrs)
        except yum.Errors.InstallError:
            log.debug("no package matching %s" %(pkg,))
            return 0

    def deselectPackage(self, pkg, *args):
        sp = pkg.rsplit(".", 2)
        txmbrs = []
        if len(sp) == 2:
            txmbrs = self.ayum.tsInfo.matchNaevr(name=sp[0], arch=sp[1])

        if len(txmbrs) == 0:
            exact, match, unmatch = yum.packages.parsePackages(self.ayum.pkgSack.returnPackages(), [pkg], casematch=1)
            for p in exact + match:
                txmbrs.append(p)

        if len(txmbrs) > 0:
            for x in txmbrs:
                self.ayum.tsInfo.remove(x.pkgtup)
                # we also need to remove from the conditionals
                # dict so that things don't get pulled back in as a result
                # of them.  yes, this is ugly.  conditionals should die.
                for req, pkgs in self.ayum.tsInfo.conditionals.iteritems():
                    if x in pkgs:
                        pkgs.remove(x)
                        self.ayum.tsInfo.conditionals[req] = pkgs
            return len(txmbrs)
        else:
            log.debug("no such package %s to remove" %(pkg,))
            return 0

    def groupListExists(self, grps):
        """Returns bool of whether all of the given groups exist."""
        for gid in grps:
            g = self.ayum.comps.return_group(gid)
            if not g:
                return False
        return True

    def groupListDefault(self, grps):
        """Returns bool of whether all of the given groups are default"""
        rc = False
        for gid in grps:
            g = self.ayum.comps.return_group(gid)
            if g and not g.default:
                return False
            elif g:
                rc = True
        return rc

    def writeKS(self, f):
        for repo in self.ayum.repos.listEnabled():
            if repo.name == "Installation Repo":
                continue

            line = "repo --name=\"%s\" " % (repo.name or repo.repoid)

            if repo.baseurl:
                line += " --baseurl=%s\n" % repo.baseurl[0]
            else:
                line += " --mirrorlist=%s\n" % repo.mirrorlist

            f.write(line)

    def writePackagesKS(self, f, anaconda):
        if anaconda.ksdata:
            f.write(anaconda.ksdata.packages.__str__())
            return

        groups = []
        installed = []
        removed = []

        # Faster to grab all the package names up front rather than call
        # searchNevra in the loop below.
        allPkgNames = map(lambda pkg: pkg.name, self.ayum.pkgSack.returnPackages())
        allPkgNames.sort()

        # On CD/DVD installs, we have one transaction per CD and will end up
        # checking allPkgNames against a very short list of packages.  So we
        # have to reset to media #0, which is an all packages transaction.
        old = self.ayum.tsInfo.curmedia
        self.ayum.tsInfo.curmedia = 0

        self.ayum.tsInfo.makelists()
        txmbrNames = map (lambda x: x.name, self.ayum.tsInfo.getMembers())

        self.ayum.tsInfo.curmedia = old

        if len(self.ayum.tsInfo.instgroups) == 0 and len(txmbrNames) == 0:
            return

        f.write("\n%packages\n")

        for grp in filter(lambda x: x.selected, self.ayum.comps.groups):
            groups.append(grp.groupid)

            defaults = grp.default_packages.keys() + grp.mandatory_packages.keys()
            optionals = grp.optional_packages.keys()

            for pkg in filter(lambda x: x in defaults and (not x in txmbrNames and x in allPkgNames), grp.packages):
                removed.append(pkg)

            for pkg in filter(lambda x: x in txmbrNames, optionals):
                installed.append(pkg)

        for grp in groups:
            f.write("@%s\n" % grp)

        for pkg in installed:
            f.write("%s\n" % pkg)

        for pkg in removed:
            f.write("-%s\n" % pkg)

        f.write("%end")

    def writeConfiguration(self):
        return

    def getRequiredMedia(self):
        return self.ayum.tsInfo.reqmedia.keys()

class DownloadHeaderProgress:
    def __init__(self, intf, ayum=None):
        window = intf.progressWindow(_("Installation Starting"),
                                     _("Starting installation process"),
                                     1.0, 0.01)
        self.window = window
        self.ayum = ayum
        self.current = self.loopstart = 0
        self.incr = 1

        if self.ayum is not None and self.ayum.tsInfo is not None:
            self.numpkgs = len(self.ayum.tsInfo.getMembers())
            if self.numpkgs != 0:
                self.incr = (1.0 / self.numpkgs) * (1.0 - self.loopstart)
        else:
            self.numpkgs = 0

        self.refresh()

        self.restartLoop = self.downloadHeader = self.transactionPopulation = self.refresh
        self.procReq = self.procConflict = self.unresolved = self.noop

    def noop(self, *args, **kwargs):
        pass

    def pkgAdded(self, *args):
        if self.numpkgs:
            self.set(self.current + self.incr)

    def pop(self):
        self.window.pop()

    def refresh(self, *args):
        self.window.refresh()

    def set(self, value):
        self.current = value
        self.window.set(self.current)

class YumDepSolveProgress:
    def __init__(self, intf, ayum = None):
        window = intf.progressWindow(_("Dependency Check"),
                                     _("Checking dependencies in packages selected for installation"),
                                     1.0, 0.01)
        self.window = window

        self.numpkgs = None
        self.loopstart = None
        self.incr = None
        self.ayum = ayum
        self.current = 0

        self.restartLoop = self.downloadHeader = self.transactionPopulation = self.refresh
        self.procReq = self.procConflict = self.unresolved = self.noop

    def tscheck(self, num = None):
        self.refresh()
        if num is None and self.ayum is not None and self.ayum.tsInfo is not None:
            num = len(self.ayum.tsInfo.getMembers())

        if num:
            self.numpkgs = num
            self.loopstart = self.current
            self.incr = (1.0 / num) * ((1.0 - self.loopstart) / 2)

    def pkgAdded(self, *args):
        if self.numpkgs:
            self.set(self.current + self.incr)

    def noop(self, *args, **kwargs):
        pass

    def refresh(self, *args):
        self.window.refresh()

    def set(self, value):
        self.current = value
        self.window.set(self.current)

    def start(self):
        self.set(0.0)
        self.refresh()

    def end(self):
        self.window.set(1.0)
        self.window.refresh()

    def pop(self):
        self.window.pop()
