#!/usr/bin/python
# Licensed under the GNU General Public License Version 2
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

# Copyright (C) 2007-2009
#    Tim Lauridsen <timlau@fedoraproject.org>
#    Seth Vidal <skvidal@fedoraproject.org>
#    Luke Macken <lmacken@redhat.com>
#    James Bowes <jbowes@dangerouslyinc.com>
#    Robin Norwood <rnorwood@redhat.com>
#
# Copyright (C) 2007-2010
#    Richard Hughes <richard@hughsie.com>
#
# Copyright (C) 2009
#    MediaGrabber, based on the logic of pirut by Jeremy Katz <katzj@redhat.com>
#    Muayyad Alsadi <alsadi@ojuba.org>

# imports
from packagekit.backend import *
from packagekit.progress import *
from packagekit.enums import *
from packagekit.package import PackagekitPackage
import yum
from urlgrabber.progress import BaseMeter, format_number
from urlgrabber.grabber import URLGrabber, URLGrabError
from yum.rpmtrans import RPMBaseCallback
from yum.constants import *
from yum.update_md import UpdateMetadata
from yum.callbacks import *
from yum.misc import prco_tuple_to_string, unique
from yum.packages import YumLocalPackage, parsePackages
from yum.packageSack import MetaSack
from yum.Errors import YumBaseError
import rpmUtils
import exceptions
import types
import signal
import time
import os.path
import logging
import socket
import gio

import tarfile
import tempfile
import shutil
import ConfigParser

from yumFilter import *
from yumComps import *

# Global vars
yumbase = None
progress = PackagekitProgress()  # Progress object to store the progress

MetaDataMap = {
    'repomd'        : STATUS_DOWNLOAD_REPOSITORY,
    'primary'       : STATUS_DOWNLOAD_PACKAGELIST,
    'filelists'     : STATUS_DOWNLOAD_FILELIST,
    'other'         : STATUS_DOWNLOAD_CHANGELOG,
    'comps'         : STATUS_DOWNLOAD_GROUP,
    'updateinfo'    : STATUS_DOWNLOAD_UPDATEINFO
}

StatusPercentageMap = {
    STATUS_DEP_RESOLVE : 5,
    STATUS_DOWNLOAD    : 10,
    STATUS_SIG_CHECK   : 40,
    STATUS_TEST_COMMIT : 45,
    STATUS_INSTALL     : 55,
    STATUS_CLEANUP     : 95
}

# this isn't defined in yum as it's only used in the rollback plugin
TS_REPACKAGING = 'repackaging'

# Map yum transactions with pk info enums
TransactionsInfoMap = {
    TS_UPDATE       : INFO_UPDATING,
    TS_ERASE        : INFO_REMOVING,
    TS_INSTALL      : INFO_INSTALLING,
    TS_TRUEINSTALL  : INFO_INSTALLING,
    TS_OBSOLETED    : INFO_OBSOLETING,
    TS_OBSOLETING   : INFO_INSTALLING,
    TS_UPDATED      : INFO_CLEANUP
}

# Map yum transactions with pk state enums
TransactionsStateMap = {
    TS_UPDATE       : STATUS_UPDATE,
    TS_ERASE        : STATUS_REMOVE,
    TS_INSTALL      : STATUS_INSTALL,
    TS_TRUEINSTALL  : STATUS_INSTALL,
    TS_OBSOLETED    : STATUS_OBSOLETE,
    TS_OBSOLETING   : STATUS_INSTALL,
    TS_UPDATED      : STATUS_CLEANUP,
    TS_REPACKAGING  : STATUS_REPACKAGING
}

class GPGKeyNotImported(exceptions.Exception):
    pass

def sigquit(signum, frame):
    if yumbase:
        yumbase.closeRpmDB()
        yumbase.doUnlock(YUM_PID_FILE)
    sys.exit(1)

def _to_unicode(txt, encoding='utf-8'):
    if isinstance(txt, YumBaseError):
        txt = unicode(txt)
    elif isinstance(txt, basestring):
        if not isinstance(txt, unicode):
            txt = unicode(txt, encoding, errors='replace')
    return txt

def _get_package_ver(po):
    ''' return the a ver as epoch:version-release or version-release, if epoch=0'''
    ver = ''
    if po.epoch != '0':
        ver = "%s:%s-%s" % (po.epoch, po.version, po.release)
    elif po.release:
        ver = "%s-%s" % (po.version, po.release)
    elif po.version:
        ver = po.version
    return ver

def _format_package_id(package_id):
    """
    Convert 'hal;0.5.8;i386;fedora' to 'hal-0.5.8-fedora(i386)'
    """
    parts = package_id.split(';')
    if len(parts) != 4:
        return "incorrect package_id: %s" % package_id
    return "%s-%s(%s)%s" % (parts[0], parts[1], parts[2], parts[3])

def _format_str(text):
    """
    Convert a multi line string to a list separated by ';'
    """
    if text:
        lines = text.split('\n')
        return ";".join(lines)
    else:
        return ""

def _format_list(lst):
    """
    Convert a multi line string to a list separated by ';'
    """
    if lst:
        return ";".join(lst)
    else:
        return ""

def _getEVR(idver):
    '''
    get the e, v, r from the package id version
    '''
    cpos = idver.find(':')
    if cpos != -1:
        epoch = idver[:cpos]
        idver = idver[cpos+1:]
    else:
        epoch = '0'
    try:
        (version, release) = tuple(idver.split('-'))
    except ValueError, e:
        version = '0'
        release = '0'
    return epoch, version, release

def _truncate(text, length, etc='...'):
    if len(text) < length:
        return text
    else:
        return text[:length] + etc

def _is_development_repo(repo):
    if repo.endswith('-debuginfo'):
        return True
    if repo.endswith('-debug'):
        return True
    if repo.endswith('-development'):
        return True
    if repo.endswith('-source'):
        return True
    return False

def _format_msgs(msgs):
    if isinstance(msgs, basestring):
        msgs = msgs.split('\n')

    # yum can pass us structures (!) in the message field
    try:
        text = ";".join(msgs)
    except exceptions.TypeError, e:
        text = str(msgs)
    except Exception, e:
        text = _format_str(traceback.format_exc())

    text = _truncate(text, 1024)
    text = text.replace(";Please report this error in bugzilla", "")
    text = text.replace("Missing Dependency: ", "")
    text = text.replace(" (installed)", "")
    return text

def _get_cmdline_for_pid(pid):
    if not pid:
        return "invalid"
    cmdlines = open("/proc/%d/cmdline" % pid).read().split('\0')
    cmdline = " ".join(cmdlines).strip(' ')
    return cmdline

class PackageKitYumBackend(PackageKitBaseBackend, PackagekitPackage):

    def __init__(self, args, lock=True):
        signal.signal(signal.SIGQUIT, sigquit)
        PackageKitBaseBackend.__init__(self, args)
        try:
            self.yumbase = PackageKitYumBase(self)
        except PkError, e:
            self.error(e.code, e.details)

        # load the config file
        config = ConfigParser.ConfigParser()
        try:
            config.read('/etc/PackageKit/Yum.conf')
        except Exception, e:
            raise PkError(ERROR_REPO_CONFIGURATION_ERROR, "Failed to load Yum.conf: %s" % _to_unicode(e))

        # if this key does not exist, it's not fatal
        try:
            self.system_packages = config.get('Backend', 'SystemPackages').split(';')
        except ConfigParser.NoOptionError, e:
            self.system_packages = []
        except Exception, e:
            raise PkError(ERROR_REPO_CONFIGURATION_ERROR, "Failed to load Yum.conf: %s" % _to_unicode(e))
        try:
            self.infra_packages = config.get('Backend', 'InfrastructurePackages').split(';')
        except ConfigParser.NoOptionError, e:
            self.infra_packages = []
        except Exception, e:
            raise PkError(ERROR_REPO_CONFIGURATION_ERROR, "Failed to load Yum.conf: %s" % _to_unicode(e))

        # get the lock early
        if lock:
            self.doLock()

        self.package_summary_cache = {}
        self.comps = yumComps(self.yumbase)
        if not self.comps.connect():
            self.refresh_cache(True)
            if not self.comps.connect():
                self.error(ERROR_GROUP_LIST_INVALID, 'comps categories could not be loaded')

        # timeout a socket after this much time
        timeout = 15.0
        socket.setdefaulttimeout(timeout)

        # use idle bandwidth by setting congestion control algorithm to TCP Low Priority
        if self.background:
            socket.TCP_CONGESTION = 13
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_CONGESTION, "lp")
            except socket.error, e:
                pass

        # we only check these types
        self.transaction_sig_check_map = [TS_UPDATE, TS_INSTALL, TS_TRUEINSTALL, TS_OBSOLETING]

        # this is global so we can catch sigquit and closedown
        yumbase = self.yumbase
        try:
            self._setup_yum()
        except PkError, e:
            self.error(e.code, e.details)

    def details(self, package_id, summary, package_license, group, desc, url, bytes):
        '''
        Send 'details' signal
        @param id: The package ID name, e.g. openoffice-clipart;2.6.22;ppc64;fedora
        @param license: The license of the package
        @param group: The enumerated group
        @param desc: The multi line package description
        @param url: The upstream project homepage
        @param bytes: The size of the package, in bytes
        convert the description to UTF before sending
        '''
        desc = _to_unicode(desc)
        PackageKitBaseBackend.details(self, package_id, summary, package_license, group, desc, url, bytes)

    def package(self, package_id, status, summary):
        '''
        send 'package' signal
        @param info: the enumerated INFO_* string
        @param id: The package ID name, e.g. openoffice-clipart;2.6.22;ppc64;fedora
        @param summary: The package Summary
        convert the summary to UTF before sending
        '''
        summary = _to_unicode(summary)

        # maintain a dictionary of the summary text so we can use it when rpm
        # is giving up package names without summaries
        (name, idver, a, repo) = self.get_package_from_id(package_id)
        if len(summary) > 0:
            self.package_summary_cache[name] = summary
        else:
            if self.package_summary_cache.has_key(name):
                summary = self.package_summary_cache[name]

        PackageKitBaseBackend.package(self, package_id, status, summary)

    def category(self, parent_id, cat_id, name, summary, icon):
        '''
        Send 'category' signal
        parent_id : A parent id, e.g. "admin" or "" if there is no parent
        cat_id    : a unique category id, e.g. "admin;network"
        name      : a verbose category name in current locale.
        summery   : a summary of the category in current locale.
        icon      : an icon name to represent the category
        '''
        name = _to_unicode(name)
        summary = _to_unicode(summary)
        PackageKitBaseBackend.category(self, parent_id, cat_id, name, summary, icon)

    def doLock(self):
        ''' Lock Yum'''
        retries = 0
        cmdline = None
        while not self.isLocked():
            try: # Try to lock yum
                self.yumbase.doLock(YUM_PID_FILE)
                PackageKitBaseBackend.doLock(self)
                self.allow_cancel(False)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except yum.Errors.LockError, e:
                self.allow_cancel(True)
                self.status(STATUS_WAITING_FOR_LOCK)

                # get the command line of the other thing
                if not cmdline:
                    cmdline = _get_cmdline_for_pid(e.pid)

                # if it's us, kill it as it's from another instance where the daemon crashed
                if cmdline.find("yumBackend.py") != -1:
                    self.message(MESSAGE_BACKEND_ERROR, "killing pid %i, as old instance" % e.pid)
                    os.kill(e.pid, signal.SIGQUIT)

                # wait a little time, and try again
                time.sleep(2)
                retries += 1

                # give up, and print process information
                if retries > 100:
                    msg = "The other process has the command line '%s' (PID %i)" % (cmdline, e.pid)
                    self.error(ERROR_CANNOT_GET_LOCK, "Yum is locked by another application. %s" % _to_unicode(msg))

    def unLock(self):
        ''' Unlock Yum'''
        if self.isLocked():
            PackageKitBaseBackend.unLock(self)
            try:
                self.yumbase.closeRpmDB()
                self.yumbase.doUnlock(YUM_PID_FILE)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

    def _do_meta_package_search(self, filters, values):
        grps = self.comps.get_meta_packages()
        for grpid in grps:
            for value in values:
                if value in grpid:
                    self._show_meta_package(grpid, filters)

    def set_locale(self, code):
        '''
        Implement the set-locale functionality
        Needed to be implemented in a sub class
        '''
        self.lang = code

    def _do_search(self, searchlist, filters, values):
        '''
        Search for yum packages
        @param searchlist: The yum package fields to search in
        @param filters: package types to search (all, installed, available)
        @param values: key to seach for
        '''
        pkgfilter = YumFilter(filters)
        package_list = []

        # get collection objects
        if FILTER_NOT_COLLECTIONS not in filters:
            self._do_meta_package_search(filters, values)

        # return, as we only want collection objects
        if FILTER_COLLECTIONS not in filters:
            installed = []
            available = []
            try:
                res = self.yumbase.searchGenerator(searchlist, values)
                for (pkg, inst) in res:
                    if pkg.repo.id.startswith('installed'):
                        installed.append(pkg)
                    else:
                        available.append(pkg)
            except yum.Errors.RepoError, e:
                raise PkError(ERROR_NO_CACHE, "failed to use search generator: %s" %_to_unicode(e))
            except exceptions.IOError, e:
                raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            else:
                pkgfilter.add_installed(installed)
                pkgfilter.add_available(available)

                # we couldn't do this when generating the list
                package_list = pkgfilter.get_package_list()
                self._show_package_list(package_list)

    def _show_package_list(self, lst):
        for (pkg, status) in lst:
            self._show_package(pkg, status)

    def search_name(self, filters, values):
        '''
        Implement the search-name functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)

        searchlist = ['name']
        self.status(STATUS_QUERY)
        try:
            self.yumbase.doConfigSetup(errorlevel=0, debuglevel=0)# Setup Yum Config
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        try:
            self._do_search(searchlist, filters, values)
        except PkError, e:
            self.error(e.code, e.details, exit=False)

    def search_details(self, filters, values):
        '''
        Implement the search-details functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        try:
            self.yumbase.doConfigSetup(errorlevel=0, debuglevel=0)# Setup Yum Config
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)

        searchlist = ['name', 'summary', 'description', 'group']
        self.status(STATUS_QUERY)
        try:
            self._do_search(searchlist, filters, values)
        except PkError, e:
            self.error(e.code, e.details, exit=False)

    def _get_installed_from_names(self, name_list):
        found = []
        for package in name_list:
            try:
                pkgs = self.yumbase.rpmdb.searchNevra(name=package)
            except exceptions.IOError, e:
                raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            else:
                found.extend(pkgs)
        return found

    def _get_available_from_names(self, name_list):
        pkgs = None
        try:
            pkgs = self.yumbase.pkgSack.searchNames(names=name_list)
        except yum.Errors.RepoError, e:
            raise PkError(ERROR_NO_CACHE, "failed to search names: %s" %_to_unicode(e))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        return pkgs

    def _handle_repo_group_search_using_yumdb(self, repo_id, filters):
        """
        Handle the special repo groups
        """
        self.percentage(None)
        pkgfilter = YumFilter(filters)
        available = []
        installed = []

        pkgs = []
        # get installed packages that came from this repo
        try:
            pkgs = self.yumbase.rpmdb
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        for pkg in pkgs:
            if pkg.yumdb_info.get('from_repo') == repo_id:
                pkgfilter.add_installed([pkg])

        # find the correct repo
        try:
            repos = self.yumbase.repos.findRepos(repo_id)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        if len(repos) == 0:
            raise PkError(ERROR_REPO_NOT_FOUND, "cannot find repo %s" % repo_id)

        # the repo might have been disabled if it is no longer contactable
        if not repos[0].isEnabled():
            raise PkError(ERROR_PACKAGE_NOT_FOUND, '%s cannot be found as %s is disabled' % (repo_id, repos[0].id))

        # populate the sack with data
        try:
            self.yumbase.repos.populateSack(repo_id)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        for pkg in repos[0].sack:
            available.append(pkg)

        # add list to filter
        pkgfilter.add_installed(installed)
        pkgfilter.add_available(available)
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)
        self.percentage(100)

    def _handle_repo_group_search(self, repo_id, filters):
        """
        Handle the special repo groups
        This is much slower than using _handle_repo_group_search_using_yumdb()
        as we have to resolve each package in the repo to see if it's
        installed.
        Of course, on RHEL5, there is no yumdb.
        """
        self.percentage(None)
        pkgfilter = YumFilter(filters)
        available = []
        installed = []

        # find the correct repo
        try:
            repos = self.yumbase.repos.findRepos(repo_id)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        if len(repos) == 0:
            raise PkError(ERROR_REPO_NOT_FOUND, "cannot find repo %s" % repo_id)
        print "found repos"

        # the repo might have been disabled if it is no longer contactable
        if not repos[0].isEnabled():
            raise PkError(ERROR_PACKAGE_NOT_FOUND, '%s cannot be found as %s is disabled' % (repo_id, repos[0].id))

        # populate the sack with data
        try:
            self.yumbase.repos.populateSack(repo_id)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        for pkg in repos[0].sack:
            try:
                instpo = self.yumbase.rpmdb.searchNevra(name=pkg.name, epoch=pkg.epoch, ver=pkg.ver, rel=pkg.rel, arch=pkg.arch)
            except exceptions.IOError, e:
                raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            if self._is_inst(pkg):
                installed.append(instpo[0])
            else:
                available.append(pkg)

        # add list to filter
        pkgfilter.add_installed(installed)
        pkgfilter.add_available(available)
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)
        self.percentage(100)

    def _handle_newest(self, filters):
        """
        Handle the special newest group
        """
        self.percentage(None)
        pkgfilter = YumFilter(filters)
        pkgs = []
        try:
            ygl = self.yumbase.doPackageLists(pkgnarrow='recent')
            pkgs.extend(ygl.recent)
        except yum.Errors.RepoError, e:
            raise PkError(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            installed = []
            available = []
            for pkg in pkgs:
                try:
                    instpo = self.yumbase.rpmdb.searchNevra(name=pkg.name, epoch=pkg.epoch, ver=pkg.ver, rel=pkg.rel, arch=pkg.arch)
                except exceptions.IOError, e:
                    raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                if len(instpo) > 0:
                    installed.append(instpo[0])
                else:
                    available.append(pkg)

            # add list to filter
            pkgfilter.add_installed(installed)
            pkgfilter.add_available(available)
            package_list = pkgfilter.get_package_list()
            self._show_package_list(package_list)
            self.percentage(100)

    def _handle_collections(self, filters):
        """
        Handle the special collection group
        """
        # Fixme: Add some real code.
        self.percentage(None)
        collections = self.comps.get_meta_packages()
        if len(collections) == 0:
            raise PkError(ERROR_GROUP_LIST_INVALID, 'No groups could be found. A cache refresh should fix this.')

        pct = 20
        old_pct = -1
        step = (100.0 - pct) / len(collections)
        for col in collections:
            self._show_meta_package(col, filters)
            pct += step
            if int(pct) != int(old_pct):
                self.percentage(pct)
                old_pct = pct
        self.percentage(100)

    def _show_meta_package(self, grpid, filters):
        show_avail = FILTER_INSTALLED not in filters
        show_inst = FILTER_NOT_INSTALLED not in filters
        package_id = "%s;;;meta" % grpid
        try:
            grp = self.yumbase.comps.return_group(grpid)
        except yum.Errors.RepoError, e:
            raise PkError(ERROR_NO_CACHE, "failed to get groups from comps: %s" % _to_unicode(e))
        except yum.Errors.GroupsError, e:
            raise PkError(ERROR_GROUP_NOT_FOUND, "failed to find group '%s': %s" % (package_id, _to_unicode(e)))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            if grp:
                name = grp.nameByLang(self.lang)
                if grp.installed:
                    if show_inst:
                        self.package(package_id, INFO_COLLECTION_INSTALLED, name)
                else:
                    if show_avail:
                        self.package(package_id, INFO_COLLECTION_AVAILABLE, name)

    def search_group(self, filters, values):
        '''
        Implement the search-group functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.allow_cancel(True)
        try:
            self.yumbase.doConfigSetup(errorlevel=0, debuglevel=0)# Setup Yum Config
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        self.yumbase.conf.cache = 0 # TODO: can we just look in the cache?
        self.status(STATUS_QUERY)
        package_list = [] #we can't do emitting as found if we are post-processing
        pkgfilter = YumFilter(filters)

        # handle collections
        if GROUP_COLLECTIONS in values:
            try:
                self._handle_collections(filters)
            except PkError, e:
                self.error(e.code, e.details, exit=False)
            return

        # handle newest packages
        if GROUP_NEWEST in values:
            try:
                self._handle_newest(filters)
            except PkError, e:
                self.error(e.code, e.details, exit=False)
            return

        # handle repo groups
        if 'repo:' in values[0]:
            try:
                self._handle_repo_group_search_using_yumdb(values[0].replace('repo:',''), filters)
            except PkError, e:
                self.error(e.code, e.details, exit=False)
            return

        # for each search term
        for value in values:
            # handle dynamic groups (yum comps group)
            if value[0] == '@':
                cat_id = value[1:]
                 # get the packagelist for this group
                all_packages = self.comps.get_meta_package_list(cat_id)
            else: # this is an group_enum
                # get the packagelist for this group enum
                all_packages = self.comps.get_package_list(value)

            # group don't exits, just bail out
            if not all_packages:
                continue

            if FILTER_NOT_INSTALLED not in filters:
                try:
                    pkgfilter.add_installed(self._get_installed_from_names(all_packages))
                except PkError, e:
                    self.error(e.code, e.details, exit=False)
                    return

            if FILTER_INSTALLED not in filters:
                try:
                    pkgfilter.add_available(self._get_available_from_names(all_packages))
                except PkError, e:
                    self.error(e.code, e.details, exit=False)
                    return

        # we couldn't do this when generating the list
        package_list = pkgfilter.get_package_list()

        self.percentage(90)
        self._show_package_list(package_list)

        self.percentage(100)

    def get_packages(self, filters):
        '''
        Search for yum packages
        @param searchlist: The yum package fields to search in
        @param filters: package types to search (all, installed, available)
        '''
        self.status(STATUS_QUERY)
        self.allow_cancel(True)
        try:
            self.yumbase.doConfigSetup(errorlevel=0, debuglevel=0)# Setup Yum Config
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        self.yumbase.conf.cache = 0 # TODO: can we just look in the cache?

        package_list = [] #we can't do emitting as found if we are post-processing
        pkgfilter = YumFilter(filters)

        # Now show installed packages.
        try:
            pkgs = self.yumbase.rpmdb
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        pkgfilter.add_installed(pkgs)

        # Now show available packages.
        if FILTER_INSTALLED not in filters:
            try:
                pkgs = self.yumbase.pkgSack
            except yum.Errors.RepoError, e:
                self.error(ERROR_NO_CACHE, "failed to get package sack: %s" %_to_unicode(e), exit=False)
                return
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            else:
                pkgfilter.add_available(pkgs)

        # we couldn't do this when generating the list
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)

    def search_file(self, filters, values):
        '''
        Implement the search-file functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_QUERY)

        #self.yumbase.conf.cache = 0 # TODO: can we just look in the cache?
        pkgfilter = YumFilter(filters)

        # Check installed for file
        if not FILTER_NOT_INSTALLED in filters:
            for value in values:
                try:
                    pkgs = self.yumbase.rpmdb.searchFiles(value)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                pkgfilter.add_installed(pkgs)

        # Check available for file
        if not FILTER_INSTALLED in filters:
            for value in values:
                try:
                    # we don't need the filelists as we're not globbing
                    pkgs = self.yumbase.pkgSack.searchFiles(value)
                except yum.Errors.RepoError, e:
                    self.error(ERROR_NO_CACHE, "failed to search sack: %s" %_to_unicode(e), exit=False)
                    return
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                else:
                    pkgfilter.add_available(pkgs)

        # we couldn't do this when generating the list
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)

    def _get_provides_query(self, provides_type, value):
        # gets a list of provides

        # old standard
        if value.startswith("gstreamer0.10(") or value.startswith("gstreamer1("):
            return [ value ]

        # new standard
        if provides_type == PROVIDES_CODEC:
            return [ "gstreamer0.10(%s)" % value ]
        if provides_type == PROVIDES_FONT:
            return [ "font(%s)" % value ]
        if provides_type == PROVIDES_MIMETYPE:
            return [ "mimehandler(%s)" % value ]
        if provides_type == PROVIDES_POSTSCRIPT_DRIVER:
            return [ "postscriptdriver(%s)" % value ]
        if provides_type == PROVIDES_PLASMA_SERVICE:
            # We need to allow the Plasma version to be specified.
            if value.startswith("plasma"):
                return [ value ]
            # For compatibility, we default to plasma4.
            return [ "plasma4(%s)" % value ]
        if provides_type == PROVIDES_ANY:
            provides = []
            provides.append(self._get_provides_query(PROVIDES_CODEC, value)[0])
            provides.append(self._get_provides_query(PROVIDES_FONT, value)[0])
            provides.append(self._get_provides_query(PROVIDES_MIMETYPE, value)[0])
            provides.append(self._get_provides_query(PROVIDES_POSTSCRIPT_DRIVER, value)[0])
            provides.append(self._get_provides_query(PROVIDES_PLASMA_SERVICE, value)[0])
            provides.append(value)
            return provides

        # not supported
        raise PkError(ERROR_NOT_SUPPORTED, "this backend does not support '%s' provides" % provides_type)

    def what_provides(self, filters, provides_type, values):
        '''
        Implement the what-provides functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_QUERY)
        values_provides = []

        pkgfilter = YumFilter(filters)

        try:
            for value in values:
                provides = self._get_provides_query(provides_type, value)
                for provide in provides:
                    values_provides.append(provide)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
        else:
            # there may be multiple provide strings
            for provide in values_provides:
                # Check installed packages for provide
                try:
                    pkgs = self.yumbase.rpmdb.searchProvides(provide)
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                else:
                    pkgfilter.add_installed(pkgs)

                    if not FILTER_INSTALLED in filters:
                        # Check available packages for provide
                        try:
                            pkgs = self.yumbase.pkgSack.searchProvides(provide)
                        except yum.Errors.RepoError, e:
                            self.error(ERROR_NO_CACHE, "failed to get provides for sack: %s" %_to_unicode(e), exit=False)
                            return
                        except exceptions.IOError, e:
                            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                        except Exception, e:
                            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                        else:
                            pkgfilter.add_available(pkgs)

        # we couldn't do this when generating the list
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)

    def get_categories(self):
        '''
        Implement the get-categories functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.percentage(None)
        self.status(STATUS_QUERY)
        self.allow_cancel(True)
        cats = []
        try:
            cats = self.yumbase.comps.categories
        except yum.Errors.RepoError, e:
            self.error(ERROR_NO_CACHE, "failed to get comps list: %s" %_to_unicode(e), exit=False)
        except yum.Errors.GroupsError, e:
            self.error(ERROR_GROUP_LIST_INVALID, "Failed to get groups list: %s" %_to_unicode(e), exit=False)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            if len(cats) == 0:
                self.error(ERROR_GROUP_LIST_INVALID, "no comps categories", exit=False)
                return
            for cat in cats:
                cat_id = cat.categoryid
                # yum >= 3.2.10
                # name = cat.nameByLang(self.lang)
                # summary = cat.descriptionByLang(self.lang)
                name = _to_unicode(cat.name)
                summary = _to_unicode(cat.description)
                fn = "/usr/share/pixmaps/comps/%s.png" % cat_id
                if os.access(fn, os.R_OK):
                    icon = cat_id
                else:
                    icon = "image-missing"
                self.category("", cat_id, name, summary, icon)
                self._get_groups(cat_id)

        # also add the repo category objects
        self.category("", 'repo:', 'Software Sources', 'Packages from specific software sources', 'base-system')
        try:
            repos = self.yumbase.repos.repos.values()
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            return
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            return
        for repo in repos:
            if repo.isEnabled():
                self.category("repo:", "repo:" + repo.id, repo.name, 'Packages from ' + repo.name, 'base-system')

    def _get_groups(self, cat_id):
        '''
        Implement the get-collections functionality
        '''
        self.status(STATUS_QUERY)
        self.allow_cancel(True)
        if cat_id:
            cats = [cat_id]
        else:
            cats =  [cat.categoryid for cat in self.yumbase.comps.categories]
        for cat in cats:
            grps = []
            for grp_id in self.comps.get_groups(cat):
                try:
                    grp = self.yumbase.comps.return_group(grp_id)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                if grp:
                    grps.append(grp)
            for grp in sorted(grps):
                grp_id = grp.groupid
                cat_id_name = "@%s" % (grp_id)
                name = grp.nameByLang(self.lang)
                summary = grp.descriptionByLang(self.lang)
                icon = "image-missing"
                fn = "/usr/share/pixmaps/comps/%s.png" % grp_id
                if os.access(fn, os.R_OK):
                    icon = grp_id
                else:
                    fn = "/usr/share/pixmaps/comps/%s.png" % cat_id
                    if os.access(fn, os.R_OK):
                        icon = cat_id
                self.category(cat, cat_id_name, name, summary, icon)

    def download_packages(self, directory, package_ids):
        '''
        Implement the download-packages functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.status(STATUS_DOWNLOAD)
        percentage = 0
        bump = 100 / len(package_ids)

        # download each package
        for package_id in package_ids:
            self.percentage(percentage)
            try:
                pkg, inst = self._findPackage(package_id)
            except PkError, e:
                self.error(e.code, e.details, exit=True)
                return
            # if we couldn't map package_id -> pkg
            if not pkg:
                self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, "Could not find the package %s" % package_id)
                continue

            n, a, e, v, r = pkg.pkgtup
            try:
                packs = self.yumbase.pkgSack.searchNevra(n, e, v, r, a)
            except yum.Errors.RepoError, e:
                self.error(ERROR_NO_CACHE, "failed to search package sack: %s" %_to_unicode(e), exit=False)
                return
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

            # if we couldn't map package_id -> pkg
            if len(packs) == 0:
                self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, "Could not find a match for package %s" % package_id)
                continue

            # choose the first entry, as the same NEVRA package in multiple repos is fine
            pkg_download = packs[0]
            self._show_package(pkg_download, INFO_DOWNLOADING)
            try:
                repo = self.yumbase.repos.getRepo(pkg_download.repoid)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            remote = pkg_download.returnSimple('relativepath')
            if directory:
                local = os.path.basename(remote)
                if not os.path.exists(directory):
                    self.error(ERROR_PACKAGE_DOWNLOAD_FAILED, "No destination directory exists", exit=False)
                    return
                local = os.path.join(directory, local)
                if (os.path.exists(local) and os.path.getsize(local) == int(pkg_download.returnSimple('packagesize'))):
                    self.error(ERROR_PACKAGE_DOWNLOAD_FAILED, "Package already exists as %s" % local, exit=False)
                    return
            # Disable cache otherwise things won't download
            repo.cache = 0

            #  set the localpath we want
            if directory:
                pkg_download.localpath = local
            try:
                path = repo.getPackage(pkg_download)

                # emit the file we downloaded
                package_id_tmp = self._pkg_to_id(pkg_download)
                self.files(package_id_tmp, path)

            except yum.Errors.NoMoreMirrorsRepoError, e:
                self.error(ERROR_NO_MORE_MIRRORS_TO_TRY, "Failed to download: %s" % _to_unicode(e), exit=False)
                return
            except IOError, e:
                self.error(ERROR_PACKAGE_DOWNLOAD_FAILED, "Cannot write to file", exit=False)
                return
            percentage += bump

        # in case we don't sum to 100
        self.percentage(100)

    def _is_meta_package(self, package_id):
        grp = None
        if len(package_id.split(';')) > 1:
            # Split up the id
            (name, idver, a, repo) = self.get_package_from_id(package_id)
            isGroup = False
            if repo == 'meta':
                if name[0] == '@':
                    name = name[1:]
                try:
                    grp = self.yumbase.comps.return_group(name)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                isGroup = True
            elif name[0] == '@':
                try:
                    grp = self.yumbase.comps.return_group(name[1:])
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                isGroup = True
            if isGroup and not grp:
                self.error(ERROR_GROUP_NOT_FOUND, "group %s does not exist (searched for %s)" % (name, _format_package_id (package_id)))
                return
        return grp

    def _findPackage(self, package_id):
        '''
        find a package based on a package id (name;version;arch;repoid)
        '''
        # Bailout if meta packages, just to be sure
        if self._is_meta_package(package_id):
            return None, False

        # is this an real id?
        if len(package_id.split(';')) <= 1:
            raise PkError(ERROR_PACKAGE_ID_INVALID, "package_id '%s' cannot be parsed" % _format_package_id(package_id))

        # Split up the id
        (n, idver, a, repo) = self.get_package_from_id(package_id)
        # get e, v, r from package id version
        e, v, r = _getEVR(idver)

        if repo.startswith('installed'):
            # search the rpmdb for the nevra
            try:
                pkgs = self.yumbase.rpmdb.searchNevra(name=n, epoch=e, ver=v, rel=r, arch=a)
            except exceptions.IOError, e:
                raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            # if the package is found, then return it (do not have to match the repo_id)
            if len(pkgs) != 0:
                return pkgs[0], True
            return None, False

        # find the correct repo, and don't use yb.pkgSack.searchNevra as it
        # searches all repos and takes 66ms
        try:
            repos = self.yumbase.repos.findRepos(repo)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        if len(repos) == 0:
            raise PkError(ERROR_REPO_NOT_FOUND, "cannot find repo %s" % repo)

        # the repo might have been disabled if it is no longer contactable
        if not repos[0].isEnabled():
            raise PkError(ERROR_PACKAGE_NOT_FOUND, '%s cannot be found as %s is disabled' % (_format_package_id(package_id), repos[0].id))

        # populate the sack with data
        try:
            self.yumbase.repos.populateSack(repo)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # search the pkgSack for the nevra
        try:
            pkgs = repos[0].sack.searchNevra(name=n, epoch=e, ver=v, rel=r, arch=a)
        except yum.Errors.RepoError, e:
            raise PkError(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # nothing found
        if len(pkgs) == 0:
            return None, False

        # multiple entries
        if len(pkgs) > 1:
            self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, "more than one package match for %s" % _format_package_id(package_id))

        # return first entry
        return pkgs[0], False

    def required_by(self, filters, package_ids, recursive):
        '''
        Print a list of requires for a given package
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)

        percentage = 0
        bump = 100 / len(package_ids)
        deps_list = []
        resolve_list = []

        for package_id in package_ids:
            self.percentage(percentage)
            grp = self._is_meta_package(package_id)
            if grp:
                if not grp.installed:
                    self.error(ERROR_PACKAGE_NOT_INSTALLED, "The Group %s is not installed" % grp.groupid)
                else:
                    try:
                        txmbrs = self.yumbase.groupRemove(grp.groupid)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                    for txmbr in self.yumbase.tsInfo:
                        deps_list.append(txmbr.po)
            else:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    if e.code == ERROR_PACKAGE_NOT_FOUND:
                        self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, e.details)
                        continue
                    self.error(e.code, e.details, exit=True)
                    return
                # This simulates the removal of the package
                if inst and pkg:
                    resolve_list.append(pkg)
                    try:
                        txmbrs = self.yumbase.remove(po=pkg)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            percentage += bump

        # do the depsolve to pull in deps
        if len(self.yumbase.tsInfo) > 0  and recursive:
            try:
                rc, msgs =  self.yumbase.buildTransaction()
            except yum.Errors.RepoError, e:
                self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            for txmbr in self.yumbase.tsInfo:
                if txmbr.po not in deps_list:
                    deps_list.append(txmbr.po)

        # remove any of the original names
        for pkg in resolve_list:
            if pkg in deps_list:
                deps_list.remove(pkg)

        # each unique name, emit
        for pkg in deps_list:
            package_id = self._pkg_to_id(pkg)
            self.package(package_id, INFO_INSTALLED, pkg.summary)
        self.percentage(100)

    def _is_inst(self, pkg):
        # search only for requested arch
        try:
            ret = self.yumbase.rpmdb.installed(po=pkg)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        return ret

    def _is_inst_arch(self, pkg):
        # search for a requested arch first
        ret = self._is_inst(pkg)
        if ret:
            return True

        # then fallback to i686 if i386
        if pkg.arch == 'i386':
            pkg.arch = 'i686'
            ret = self._is_inst(pkg)
            pkg.arch = 'i386'
        return ret

    def _installable(self, pkg, ematch=False):

        """check if the package is reasonably installable, true/false"""

        try:
            exactarchlist = self.yumbase.conf.exactarchlist
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        # we look through each returned possibility and rule out the
        # ones that we obviously can't use

        if self._is_inst_arch(pkg):
            return False

        # everything installed that matches the name
        try:
            installedByKey = self.yumbase.rpmdb.searchNevra(name=pkg.name, arch=pkg.arch)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        comparable = []
        for instpo in installedByKey:
            if rpmUtils.arch.isMultiLibArch(instpo.arch) == rpmUtils.arch.isMultiLibArch(pkg.arch):
                comparable.append(instpo)
            else:
                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:
                            return True
                    else:
                        return True

                elif pkg.EVR == instpo.EVR: # same, ignore
                    return False

                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 ematch:
                        try:
                            ret = self.yumbase.allowedMultipleInstalls(pkg)
                        except exceptions.IOError, e:
                            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                        except Exception, e:
                            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                        if ret:
                            return True

        else: # we've not got any installed that match n or n+a
            return True

        return False

    def _get_best_pkg_from_list(self, pkglist):
        '''
        Gets best dep package from a list
        '''
        best = None

        # first try and find the highest EVR package that is already installed
        for pkgi in pkglist:
            n, a, e, v, r = pkgi.pkgtup
            try:
                pkgs = self.yumbase.rpmdb.searchNevra(name=n, epoch=e, ver=v, arch=a)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            for pkg in pkgs:
                if best:
                    if pkg.EVR > best.EVR:
                        best = pkg
                else:
                    best = pkg

        # then give up and see if there's one available
        if not best:
            for pkg in pkglist:
                if best:
                    if pkg.EVR > best.EVR:
                        best = pkg
                else:
                    best = pkg
        return best

    def _get_best_depends(self, pkgs, recursive):
        ''' Gets the best deps for a package
        @param pkgs: a list of package objects
        @param recursive: if we recurse
        @return: a list for yum package object providing the dependencies
        '''
        deps_list = []

        # get the dep list
        try:
            results = self.yumbase.findDeps(pkgs)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        require_list = []
        recursive_list = []

        # get the list of deps for each package
        for pkg in results.keys():
            for req in results[pkg].keys():
                reqlist = results[pkg][req]
                if not reqlist: #  Unsatisfied dependency
                    self.error(ERROR_DEP_RESOLUTION_FAILED, "the (%s) requirement could not be resolved" % prco_tuple_to_string(req), exit=False)
                    break
                require_list.append(reqlist)

        # for each list, find the best backage using a metric
        for reqlist in require_list:
            pkg = self._get_best_pkg_from_list(reqlist)
            if pkg not in pkgs:
                deps_list.append(pkg)
                if recursive and not self._is_inst(pkg):
                    recursive_list.append(pkg)

        # if the package is to be downloaded, also find its deps
        if len(recursive_list) > 0:
            pkgsdeps = self._get_best_depends(recursive_list, True)
            for pkg in pkgsdeps:
                if pkg not in pkgs:
                    deps_list.append(pkg)

        return deps_list

    def _get_group_packages(self, grp):
        '''
        Get the packages there will be installed when a comps group
        is installed
        '''
        if not grp.installed:
            try:
                txmbrs = self.yumbase.selectGroup(grp.groupid)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            try:
                txmbrs = self.yumbase.groupRemove(grp.groupid)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        pkgs = []
        for t in txmbrs:
            pkgs.append(t.po)
        if not grp.installed:
            try:
                self.yumbase.deselectGroup(grp.groupid)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            try:
                self.yumbase.groupUnremove(grp.groupid)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        return pkgs

    def _depends_on_not_installed(self, filters, package_ids, recursive):
        '''
        Gets the deps that are not installed, optimisation of depends_on
        using a yum transaction
        Returns a list of pkgs.
        '''
        percentage = 0
        bump = 100 / len(package_ids)
        deps_list = []
        resolve_list = []

        for package_id in package_ids:
            self.percentage(percentage)
            grp = self._is_meta_package(package_id)
            if grp:
                if grp.installed:
                    self.error(ERROR_PACKAGE_ALREADY_INSTALLED, "The Group %s is already installed" % grp.groupid)
                else:
                    try:
                        txmbrs = self.yumbase.selectGroup(grp.groupid)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                    for txmbr in self.yumbase.tsInfo:
                        deps_list.append(txmbr.po)
                    # unselect what we previously selected
                    try:
                        self.yumbase.deselectGroup(grp.groupid)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            else:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    self.error(e.code, e.details, exit=True)
                    return
                # This simulates the addition of the package
                if not inst and pkg:
                    resolve_list.append(pkg)
                    try:
                        txmbrs = self.yumbase.install(po=pkg)
                    except yum.Errors.RepoError, e:
                        self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            percentage += bump

        if len(self.yumbase.tsInfo) > 0 and recursive:
            try:
                rc, msgs =  self.yumbase.buildTransaction()
            except yum.Errors.RepoError, e:
                self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            if rc != 2:
                self.error(ERROR_DEP_RESOLUTION_FAILED, _format_msgs(msgs))
            else:
                for txmbr in self.yumbase.tsInfo:
                    if txmbr.po not in deps_list:
                        deps_list.append(txmbr.po)

        # make unique list
        deps_list = unique(deps_list)

        # remove any of the packages we passed in
        for package_id in package_ids:
            try:
                pkg, inst = self._findPackage(package_id)
            except PkError, e:
                self.error(e.code, e.details, exit=True)
                return
            if pkg in deps_list:
                deps_list.remove(pkg)

        # remove any that are already installed
        for pkg in deps_list:
            if self._is_inst(pkg):
                deps_list.remove(pkg)

        return deps_list

    def depends_on(self, filters, package_ids, recursive):
        '''
        Print a list of depends for a given package
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)
        pkgfilter = YumFilter(filters)

        # before we do an install we do ~installed + recursive true,
        # which we can emulate quicker by doing a transaction, but not
        # executing it
        if FILTER_NOT_INSTALLED in filters and recursive:
            pkgs = self._depends_on_not_installed (filters, package_ids, recursive)
            pkgfilter.add_available(pkgs)
            package_list = pkgfilter.get_package_list()
            self._show_package_list(package_list)
            self.percentage(100)
            return

        percentage = 0
        bump = 100 / len(package_ids)
        deps_list = []
        resolve_list = []
        grp_pkgs = []

        # resolve each package_id to a pkg object
        for package_id in package_ids:
            self.percentage(percentage)
            grp = self._is_meta_package(package_id)
            if grp:
                pkgs = self._get_group_packages(grp)
                grp_pkgs.extend(pkgs)
            else:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    if e.code == ERROR_PACKAGE_NOT_FOUND:
                        self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, e.details)
                        continue
                    self.error(e.code, e.details, exit=True)
                    return
                if pkg:
                    resolve_list.append(pkg)
                else:
                    self.error(ERROR_PACKAGE_NOT_FOUND, 'Package %s was not found' % package_id)
                    break
            percentage += bump

        if grp_pkgs:
            resolve_list.extend(grp_pkgs)
        # get the best deps -- doing recursive is VERY slow
        deps_list = self._get_best_depends(resolve_list, recursive)

        # make unique list
        deps_list = unique(deps_list)

        # If packages comes from a group, then we show them along with deps.
        if grp_pkgs:
            deps_list.extend(grp_pkgs)

        # add to correct lists
        for pkg in deps_list:
            if self._is_inst(pkg):
                pkgfilter.add_installed([pkg])
            else:
                pkgfilter.add_available([pkg])

        # we couldn't do this when generating the list
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)
        self.percentage(100)

    def _is_package_repo_signed(self, pkg):
        '''
        Finds out if the repo that contains the package is signed
        '''
        signed = False
        try:
            repo = self.yumbase.repos.getRepo(pkg.repoid)
            signed = repo.gpgcheck
        except yum.Errors.RepoError, e:
            raise PkError(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        return signed

    def _set_only_trusted(self, only_trusted):
        # if only_trusted is true, it means that we will only install/update
        # signed files and fail on unsigned ones

        if hasattr(self.yumbase, "_override_sigchecks"):
            # _override_sigchecks logic is reversed
            override_sigchecks = not only_trusted

            self.yumbase._override_sigchecks = override_sigchecks

            for repo in self.yumbase.repos.listEnabled():
                repo._override_sigchecks = override_sigchecks

        for attrname in ("gpgcheck", "repo_gpgcheck", "localpkg_gpgcheck"):
            if hasattr(self.yumbase.conf, attrname):
                setattr(self.yumbase.conf, attrname, only_trusted)

            for repo in self.yumbase.repos.listEnabled():
                if hasattr(repo, attrname):
                    setattr(repo, attrname, only_trusted)

    def refresh_cache(self, force):
        '''
        Implement the refresh_cache functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.allow_cancel(True)
        self.percentage(0)
        self.status(STATUS_REFRESH_CACHE)

        # we are working offline
        if not self.has_network:
            self.error(ERROR_NO_NETWORK, "cannot refresh cache when offline", exit=False)
            return

        pct = 0
        try:
            if len(self.yumbase.repos.listEnabled()) == 0:
                self.percentage(100)
                return

            #work out the slice for each one
            bump = (95/len(self.yumbase.repos.listEnabled()))/2

            for repo in self.yumbase.repos.listEnabled():

                # emit details for UI
                self.repo_detail(repo.id, repo.name, True)

                # is physical media
                if repo.mediaid:
                    continue
                repo.metadata_expire = 0
                self.yumbase.repos.populateSack(which=[repo.id], mdtype='metadata', cacheonly=1)
                pct += bump
                self.percentage(pct)
                self.yumbase.repos.populateSack(which=[repo.id], mdtype='filelists', cacheonly=1)
                pct += bump
                self.percentage(pct)

            self.percentage(95)
            # Setup categories/groups
            try:
                self.yumbase.doGroupSetup()
            except yum.Errors.GroupsError, e:
                pass
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            #we might have a rounding error
            self.percentage(100)

        except yum.Errors.RepoError, e:
            message = _format_msgs(e.value)
            if message.find ("No more mirrors to try") != -1:
                self.error(ERROR_NO_MORE_MIRRORS_TO_TRY, message, exit=False)
            else:
                self.error(ERROR_REPO_CONFIGURATION_ERROR, message, exit=False)
        except yum.Errors.YumBaseError, e:
            self.error(ERROR_UNKNOWN, "cannot refresh cache: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            # update the comps groups too
            self.comps.refresh()

    def resolve(self, filters, packages):
        '''
        Implement the resolve functionality
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.allow_cancel(True)
        self.percentage(None)
        try:
            self.yumbase.doConfigSetup(errorlevel=0, debuglevel=0)# Setup Yum Config
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        self.yumbase.conf.cache = 0 # TODO: can we just look in the cache?
        self.status(STATUS_QUERY)

        pkgfilter = YumFilter(filters)
        package_list = []

        # OR search
        for package in packages:
            # Get installed packages
            if FILTER_NOT_INSTALLED not in filters:
                try:
                    pkgs = self.yumbase.rpmdb.searchNevra(name=package)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                else:
                    pkgfilter.add_installed(pkgs)

            # Get available packages
            if FILTER_INSTALLED not in filters:
                try:
                    pkgs = self.yumbase.pkgSack.returnNewestByName(name=package)
                except yum.Errors.PackageSackError, e:
                    # no package of this name found, which is okay
                    pass
                except yum.Errors.RepoError, e:
                    self.error(ERROR_NO_CACHE, "failed to return newest by package sack: %s" %_to_unicode(e), exit=False)
                    return
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                else:
                    pkgfilter.add_available(pkgs)

        # is this a metapackage (a group)
        for package_id in packages:
            if package_id[0] == '@':
                grps = self.comps.get_meta_packages()
                for grpid in grps:
                    if grpid == package_id[1:]:

                        # create virtual package
                        pkg = yum.packages.PackageObject()
                        pkg.name = package_id[1:]
                        pkg.version = ''
                        pkg.release = ''
                        pkg.arch = ''
                        pkg.epoch = '0'
                        pkg.repo = 'meta'

                        # get the name and the installed status
                        try:
                            grp = self.yumbase.comps.return_group(grpid)
                        except yum.Errors.RepoError, e:
                            raise PkError(ERROR_NO_CACHE, "failed to get groups from comps: %s" % _to_unicode(e))
                        except yum.Errors.GroupsError, e:
                            raise PkError(ERROR_GROUP_NOT_FOUND, "failed to find group '%s': %s" % (package_id, _to_unicode(e)))
                        except exceptions.IOError, e:
                            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                        except Exception, e:
                            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                        pkg.summary = grp.name
                        if grp.installed:
                            pkgfilter.add_installed([pkg])
                        else:
                            pkgfilter.add_available([pkg])

        # we couldn't do this when generating the list
        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)

    def install_packages(self, transaction_flags, package_ids):
        '''
        Implement the install-packages functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(False)
        self.percentage(0)
        self.status(STATUS_RUNNING)
        txmbrs = []

        self._set_only_trusted(TRANSACTION_FLAG_ONLY_TRUSTED in transaction_flags or TRANSACTION_FLAG_SIMULATE in transaction_flags)

        for package_id in package_ids:
            grp = self._is_meta_package(package_id)
            if grp:
                if grp.installed:
                    self.error(ERROR_PACKAGE_ALREADY_INSTALLED, "This Group %s is already installed" % grp.groupid, exit=False)
                    return
                try:
                    # I'm not sure why we have to deselectGroup() before we selectGroup(), but if we don't
                    # then selectGroup returns no packages. I've already made sure that any selectGroup
                    # invokations do deselectGroup, so I'm not sure what's going on...
                    self.yumbase.deselectGroup(grp.groupid)
                    txmbr = self.yumbase.selectGroup(grp.groupid)
                    if not txmbr:
                        self.error(ERROR_GROUP_NOT_FOUND, "No packages were found in the %s group for %s." % (grp.groupid, _format_package_id(package_id)))
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                txmbrs.extend(txmbr)
            else:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    self.error(e.code, e.details, exit=True)
                    return
                if pkg and not inst:
                    txmbr = self.yumbase.install(po=pkg)
                    txmbrs.extend(txmbr)
                if inst:
                    self.error(ERROR_PACKAGE_ALREADY_INSTALLED, "The package %s is already installed" % pkg.name, exit=False)
                    return
        if txmbrs:
            for t in txmbrs:
                pkg = t.po
                # ignore transactions that do not have to be checked, e.g. obsoleted
                if t.output_state not in self.transaction_sig_check_map:
                    continue
                try:
                    signed = self._is_package_repo_signed(pkg)
                except PkError, e:
                    self.error(e.code, e.details, exit=False)
                    return
                if signed:
                    continue
                if TRANSACTION_FLAG_ONLY_TRUSTED in transaction_flags:
                    self.error(ERROR_CANNOT_INSTALL_REPO_UNSIGNED, "The package %s will not be installed from unsigned repo %s" % (pkg.name, pkg.repoid), exit=False)
                    return
                self._show_package(pkg, INFO_UNTRUSTED)
            try:
                self._runYumTransaction(transaction_flags)
            except PkError, e:
                self.error(e.code, e.details, exit=False)
        else:
            self.error(ERROR_ALL_PACKAGES_ALREADY_INSTALLED, "The packages are already all installed", exit=False)

    def _checkForNewer(self, po):
        pkgs = None
        try:
            pkgs = self.yumbase.pkgSack.returnNewestByName(name=po.name)
        except yum.Errors.PackageSackError:
            pass
        except yum.Errors.RepoError, e:
            pass
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        if pkgs:
            newest = pkgs[0]
            if newest.EVR > po.EVR:
                self.message(MESSAGE_NEWER_PACKAGE_EXISTS, "A newer version of %s is available online." % po.name)

    def install_files(self, transaction_flags, inst_files):
        '''
        Implement the install-files functionality
        Install the package containing the inst_file file
        Needed to be implemented in a sub class
        '''
        for inst_file in inst_files:
            if inst_file.endswith('.src.rpm'):
                self.error(ERROR_CANNOT_INSTALL_SOURCE_PACKAGE, 'Backend will not install a src rpm file', exit=False)
                return

        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(False)
        self.percentage(0)
        self.status(STATUS_RUNNING)

        # check we have at least one file
        if len(inst_files) == 0:
            self.error(ERROR_FILE_NOT_FOUND, 'no files specified to install', exit=False)
            return

        # check that the files still exist
        for inst_file in inst_files:
            if not os.path.exists(inst_file):
                self.error(ERROR_FILE_NOT_FOUND, '%s could not be found' % inst_file, exit=False)
                return

        # process these first
        tempdir = tempfile.mkdtemp()
        inst_packs = []

        for inst_file in inst_files:
            if inst_file.endswith('.rpm'):
                continue
            elif inst_file.endswith('.servicepack'):
                inst_packs.append(inst_file)
            else:
                self.error(ERROR_INVALID_PACKAGE_FILE, 'Only rpm files and packs are supported', exit=False)
                return

        # decompress and add the contents of any .servicepack files
        for inst_pack in inst_packs:
            inst_files.remove(inst_pack)
            pack = tarfile.TarFile(name = inst_pack, mode = "r")
            members = pack.getnames()
            for mem in members:
                pack.extract(mem, path = tempdir)
            files = os.listdir(tempdir)

            # find the metadata file
            packtype = 'unknown'
            for fn in files:
                if fn == "metadata.conf":
                    config = ConfigParser.ConfigParser()
                    config.read(os.path.join(tempdir, fn))
                    if config.has_option('PackageKit Service Pack', 'type'):
                        packtype = config.get('PackageKit Service Pack', 'type')
                    break

            # we only support update and install
            if packtype != 'install' and packtype != 'update':
                self.error(ERROR_INVALID_PACKAGE_FILE, 'no support for type %s' % packtype, exit=False)
                return

            # add the file if it's an install, or update if installed
            for fn in files:
                if fn.endswith('.rpm'):
                    inst_file = os.path.join(tempdir, fn)
                    try:
                        # read the file
                        pkg = YumLocalPackage(ts=self.yumbase.rpmdb.readOnlyTS(), filename=inst_file)
                        pkgs_local = self.yumbase.rpmdb.searchNevra(name=pkg.name)
                    except yum.Errors.MiscError:
                        self.error(ERROR_INVALID_PACKAGE_FILE, "%s does not appear to be a valid package." % inst_file)
                    except yum.Errors.YumBaseError, e:
                        self.error(ERROR_INVALID_PACKAGE_FILE, 'Package could not be decompressed')
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except:
                        self.error(ERROR_UNKNOWN, "Failed to open local file -- please report")
                    else:
                        # trying to install package that already exists
                        if len(pkgs_local) == 1 and pkgs_local[0].EVR == pkg.EVR:
                            self.message(MESSAGE_PACKAGE_ALREADY_INSTALLED, '%s is already installed and the latest version' % pkg.name)

                        # trying to install package older than already exists
                        elif len(pkgs_local) == 1 and pkgs_local[0].EVR > pkg.EVR:
                            self.message(MESSAGE_PACKAGE_ALREADY_INSTALLED, 'a newer version of %s is already installed' % pkg.name)

                        # only update if installed
                        elif packtype == 'update':
                            if len(pkgs_local) > 0:
                                inst_files.append(inst_file)

                        # only install if we passed the checks above
                        elif packtype == 'install':
                            inst_files.append(inst_file)

        if len(inst_files) == 0:
            # More than one pkg to be installed, all of them already installed
            self.error(ERROR_ALL_PACKAGES_ALREADY_INSTALLED,
                       'All of the specified packages have already been installed')

        self._set_only_trusted(TRANSACTION_FLAG_ONLY_TRUSTED in transaction_flags or TRANSACTION_FLAG_SIMULATE in transaction_flags)

        # self.yumbase.installLocal fails for unsigned packages when self.yumbase.conf.gpgcheck = 1
        # This means we don't run runYumTransaction, and don't get the GPG failure in
        # PackageKitYumBase(_checkSignatures) -- so we check here
        for inst_file in inst_files:
            try:
                po = YumLocalPackage(ts=self.yumbase.rpmdb.readOnlyTS(), filename=inst_file)
            except yum.Errors.MiscError:
                self.error(ERROR_INVALID_PACKAGE_FILE, "%s does not appear to be a valid package." % inst_file, exit=False)
                return
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            try:
                self.yumbase._checkSignatures([po], None)
            except yum.Errors.YumGPGCheckError, e:
                if TRANSACTION_FLAG_ONLY_TRUSTED in transaction_flags:
                    self.error(ERROR_MISSING_GPG_SIGNATURE, _to_unicode(e), exit=False)
                    return
                self._show_package(po, INFO_UNTRUSTED)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # common checks copied from yum
        for inst_file in inst_files:
            if not self._check_local_file(inst_file):
                return

        txmbrs = []
        try:
            for inst_file in inst_files:
                try:
                    txmbr = self.yumbase.installLocal(inst_file)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                if txmbr:
                    txmbrs.extend(txmbr)
                    self._checkForNewer(txmbr[0].po)
                    # Added the package to the transaction set
                else:
                    self.error(ERROR_LOCAL_INSTALL_FAILED, "Can't install %s as no transaction" % _to_unicode(inst_file))
            if len(self.yumbase.tsInfo) == 0:
                self.error(ERROR_LOCAL_INSTALL_FAILED, "Can't install %s" % " or ".join(inst_files), exit=False)
                return

            try:
                self._runYumTransaction(transaction_flags)
            except PkError, e:
                self.error(e.code, e.details, exit=False)
                return

        except yum.Errors.InstallError, e:
            self.error(ERROR_LOCAL_INSTALL_FAILED, _to_unicode(e))
        except (yum.Errors.RepoError, yum.Errors.PackageSackError, IOError):
            # We might not be able to connect to the internet to get
            # repository metadata, or the package might not exist.
            # Try again, (temporarily) disabling repos first.
            try:
                for repo in self.yumbase.repos.listEnabled():
                    repo.disable()

                for inst_file in inst_files:
                    try:
                        txmbr = self.yumbase.installLocal(inst_file)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                    if txmbr:
                        txmbrs.extend(txmbr)
                        if len(self.yumbase.tsInfo) > 0:
                            if not self.yumbase.tsInfo.pkgSack:
                                self.yumbase.tsInfo.pkgSack = MetaSack()
                            try:
                                self._runYumTransaction(transaction_flags)
                            except PkError, e:
                                self.error(e.code, e.details, exit=False)
                                return
                    else:
                        self.error(ERROR_LOCAL_INSTALL_FAILED, "Can't install %s" % inst_file)
            except yum.Errors.InstallError, e:
                self.error(ERROR_LOCAL_INSTALL_FAILED, _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        shutil.rmtree(tempdir)

    def _check_local_file(self, pkg):
        """
        Duplicates some of the checks that yumbase.installLocal would
        do, so we can get decent error reporting.
        """
        po = None
        try:
            po = YumLocalPackage(ts=self.yumbase.rpmdb.readOnlyTS(), filename=pkg)
        except yum.Errors.MiscError:
            self.error(ERROR_INVALID_PACKAGE_FILE, "%s does not appear to be a valid package." % pkg, exit=False)
            return False
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            return False
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            return False

        # check if wrong arch
        suitable_archs = rpmUtils.arch.getArchList()
        if po.arch not in suitable_archs:
            self.error(ERROR_INCOMPATIBLE_ARCHITECTURE, "Package %s has incompatible architecture %s. Valid architectures are %s" % (pkg, po.arch, suitable_archs), exit=False)
            return False

        # check already installed
        if self._is_inst_arch(po):
            self.error(ERROR_PACKAGE_ALREADY_INSTALLED, "The package %s is already installed" % str(po), exit=False)
            return False

        # check if excluded
        if len(self.yumbase.conf.exclude) > 0:
            exactmatch, matched, unmatched = parsePackages([po], self.yumbase.conf.exclude, casematch=1)
            if po in exactmatch + matched:
                self.error(ERROR_PACKAGE_INSTALL_BLOCKED, "Installation of %s is excluded by yum configuration." % pkg, exit=False)
                return False

        return True

    def update_packages(self, transaction_flags, package_ids):
        '''
        Implement the install functionality
        This will only work with yum 3.2.4 or higher
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(False)
        self.percentage(0)
        self.status(STATUS_RUNNING)

        self._set_only_trusted(TRANSACTION_FLAG_ONLY_TRUSTED in transaction_flags or TRANSACTION_FLAG_SIMULATE in transaction_flags)

        txmbrs = []
        try:
            for package_id in package_ids:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    if e.code == ERROR_PACKAGE_NOT_FOUND:
                        self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, e.details)
                        package_ids.remove(package_id)
                        continue
                    self.error(e.code, e.details, exit=True)
                    return
                if pkg:
                    try:
                        txmbr = self.yumbase.update(po=pkg)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                    if not txmbr:
                        self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, "could not add package update for %s: %s" % (_format_package_id(package_id), pkg))
                    else:
                        txmbrs.extend(txmbr)
        except yum.Errors.RepoError, e:
            self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e), exit=False)
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            if txmbrs:
                for t in txmbrs:
                    # ignore transactions that do not have to be checked, e.g. obsoleted
                    if t.output_state not in self.transaction_sig_check_map:
                        continue
                    pkg = t.po
                    try:
                        signed = self._is_package_repo_signed(pkg)
                    except PkError, e:
                        self.error(e.code, e.details, exit=False)
                        return
                    if signed:
                        continue
                    if TRANSACTION_FLAG_ONLY_TRUSTED in transaction_flags:
                        self.error(ERROR_CANNOT_UPDATE_REPO_UNSIGNED, "The package %s will not be updated from unsigned repo %s" % (pkg.name, pkg.repoid), exit=False)
                        return
                    self._show_package(pkg, INFO_UNTRUSTED)
                try:
                    self._runYumTransaction(transaction_flags, allow_skip_broken=True)
                except PkError, e:
                    self.error(e.code, e.details, exit=False)
            else:
                self.error(ERROR_NO_PACKAGES_TO_UPDATE, "No updates available", exit=False)

    def _check_for_reboot(self):
        md = self.updateMetadata
        for txmbr in self.yumbase.tsInfo:
            pkg = txmbr.po
            # check if package is in reboot list or flagged with reboot_suggested
            # in the update metadata and is installed/updated etc
            notice = md.get_notice((pkg.name, pkg.version, pkg.release))
            if notice and notice.get_metadata().has_key('reboot_suggested') and notice['reboot_suggested']:
                self.require_restart(RESTART_SYSTEM, self._pkg_to_id(pkg))

    def _runYumTransaction(self, transaction_flags, allow_remove_deps=None, allow_skip_broken=False):
        '''
        Run the yum Transaction
        This will only work with yum 3.2.4 or higher
        '''
        message = ''
        try:
            self.yumbase.conf.skip_broken = 0
            self.yumbase.rpmdb.auto_close = False
            rc, msgs = self.yumbase.buildTransaction()
            message = _format_msgs(msgs)
        except yum.Errors.RepoError, e:
            raise PkError(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
        except yum.Errors.PackageSackError, e:
            raise PkError(ERROR_PACKAGE_DATABASE_CHANGED, _to_unicode(e))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # if return value is 1 (error), try again with skip-broken if allowed
        if allow_skip_broken and rc == 1:
            try:
                self.yumbase.conf.skip_broken = 1
                rc, msgs = self.yumbase.buildTransaction()
                message += " : %s" % _format_msgs(msgs)
            except yum.Errors.RepoError, e:
                raise PkError(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
            except exceptions.IOError, e:
                raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # test did succeed
        self._check_for_reboot()
        if allow_remove_deps == False:
            if len(self.yumbase.tsInfo) > 1:
                retmsg = 'package could not be removed, as other packages depend on it'
                raise PkError(ERROR_DEP_RESOLUTION_FAILED, retmsg)

        # we did not succeed
        if rc != 2:
            if message.find ("is needed by") != -1:
                raise PkError(ERROR_DEP_RESOLUTION_FAILED, message)
            if message.find ("empty transaction") != -1:
                raise PkError(ERROR_NO_PACKAGES_TO_UPDATE, message)
            raise PkError(ERROR_TRANSACTION_ERROR, message)

        # abort now we have the package list
        if TRANSACTION_FLAG_SIMULATE in transaction_flags:
            package_list = []
            for txmbr in self.yumbase.tsInfo:
                if txmbr.output_state in TransactionsInfoMap.keys():
                    info = TransactionsInfoMap[txmbr.output_state]
                    package_list.append((txmbr.po, info))

            self.percentage(90)
            self._show_package_list(package_list)
            self.percentage(100)
            return

        # just download packages ready for the actual transaction
        if TRANSACTION_FLAG_ONLY_DOWNLOAD in transaction_flags:
            for txmbr in self.yumbase.tsInfo:
                if txmbr.output_state in TS_INSTALL_STATES:
                    try:
                        self._show_package(txmbr.po, INFO_DOWNLOADING)
                        self.yumbase.downloadPkgs([txmbr.po])
                    except Exception, e:
                        self.error(ERROR_PACKAGE_DOWNLOAD_FAILED, "Cannot download packages: %s" % _to_unicode(e), exit=False)
                        return
            self.percentage(100)
            return

        try:
            rpmDisplay = PackageKitCallback(self)
            callback = ProcessTransPackageKitCallback(self)
            self.yumbase.processTransaction(callback=callback,
                                  rpmDisplay=rpmDisplay)
        except yum.Errors.YumDownloadError, ye:
            raise PkError(ERROR_PACKAGE_DOWNLOAD_FAILED, _format_msgs(ye.value))
        except yum.Errors.YumGPGCheckError, ye:
            raise PkError(ERROR_BAD_GPG_SIGNATURE, _format_msgs(ye.value))
        except GPGKeyNotImported, e:
            keyData = self.yumbase.missingGPGKey
            if not keyData:
                raise PkError(ERROR_BAD_GPG_SIGNATURE, "GPG key not imported, and no GPG information was found.")
            package_id = self._pkg_to_id(keyData['po'])
            fingerprint = keyData['fingerprint']()
            hex_fingerprint = "%02x" * len(fingerprint) % tuple(map(ord, fingerprint))
            # Borrowed from http://mail.python.org/pipermail/python-list/2000-September/053490.html

            self.repo_signature_required(package_id,
                                         keyData['po'].repoid,
                                         keyData['keyurl'].replace("file://", ""),
                                         keyData['userid'],
                                         keyData['hexkeyid'],
                                         hex_fingerprint,
                                         time.ctime(keyData['timestamp']),
                                         'gpg')
            raise PkError(ERROR_GPG_FAILURE, "GPG key %s required" % keyData['hexkeyid'])
        except yum.Errors.YumBaseError, ye:
            message = _format_msgs(ye.value)
            if message.find ("conflicts with file") != -1:
                raise PkError(ERROR_FILE_CONFLICTS, message)
            if message.find ("rpm_check_debug vs depsolve") != -1:
                raise PkError(ERROR_PACKAGE_CONFLICTS, message)
            else:
                raise PkError(ERROR_TRANSACTION_ERROR, message)
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

    def remove_packages(self, transaction_flags, package_ids, allowdep, autoremove):
        '''
        Implement the remove functionality
        Needed to be implemented in a sub class
        '''
        # TODO: use autoremove
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(False)
        self.percentage(0)
        self.status(STATUS_RUNNING)

        if autoremove:
            self.message(MESSAGE_PARAMETER_INVALID, "the yum backend does not support autoremove")

        txmbrs = []
        for package_id in package_ids:
            grp = self._is_meta_package(package_id)
            if grp:
                if not grp.installed:
                    self.error(ERROR_PACKAGE_NOT_INSTALLED, "This Group %s is not installed" % grp.groupid)
                try:
                    txmbr = self.yumbase.groupRemove(grp.groupid)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                txmbrs.extend(txmbr)
            else:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    self.error(e.code, e.details, exit=True)
                    return
                if pkg and inst:
                    try:
                        txmbr = self.yumbase.remove(po=pkg)
                    except exceptions.IOError, e:
                        self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                    except Exception, e:
                        self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                    txmbrs.extend(txmbr)
                if pkg and not inst:
                    self.error(ERROR_PACKAGE_NOT_INSTALLED, "The package %s is not installed" % pkg.name)
        if txmbrs:
            # check to find any system packages
            try:
                rc, msgs =  self.yumbase.buildTransaction()
            except yum.Errors.RepoError, e:
                self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except Exception, e:
                self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            if rc != 2:
                self.error(ERROR_DEP_RESOLUTION_FAILED, _format_msgs(msgs))
            else:
                for txmbr in self.yumbase.tsInfo:
                    pkg = txmbr.po
                    if pkg.name in self.system_packages:
                        self.error(ERROR_CANNOT_REMOVE_SYSTEM_PACKAGE, "The package %s is essential to correct operation and cannot be removed using this tool." % pkg.name, exit=False)
                        return
            try:
                if not allowdep:
                    self._runYumTransaction(transaction_flags, allow_remove_deps=False)
                else:
                    self._runYumTransaction(transaction_flags, allow_remove_deps=True)
            except PkError, e:
                self.error(e.code, e.details, exit=False)
        else:
            msg = "The following packages failed to be removed: %s" % str(package_ids)
            self.error(ERROR_PACKAGE_NOT_INSTALLED, msg, exit=False)

    def _get_category(self, groupid):
        cat_id = self.comps.get_category(groupid)
        if self.yumbase.comps._categories.has_key(cat_id):
            return self.yumbase.comps._categories[cat_id]
        else:
            return None

    def get_details(self, package_ids):
        '''
        Print a detailed details for a given package
        '''
        try:
            self._check_init(lazy_cache=True)
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)

        for package_id in package_ids:
            grp = self._is_meta_package(package_id)
            if grp:
                package_id = "%s;;;meta" % grp.groupid
                desc = grp.descriptionByLang(self.lang)
                desc = desc.replace('\n\n', ';')
                desc = desc.replace('\n', ' ')
                group = GROUP_COLLECTIONS
                pkgs = self._get_group_packages(grp)
                size = 0
                for pkg in pkgs:
                    size = size + pkg.size
                self.details(package_id, "", group, desc, "", size)

            else:
                try:
                    pkg, inst = self._findPackage(package_id)
                except PkError, e:
                    if e.code == ERROR_PACKAGE_NOT_FOUND:
                        self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, e.details)
                        continue
                    self.error(e.code, e.details, exit=True)
                    return
                if pkg:
                    self._show_details_pkg(pkg)
                else:
                    self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, 'Package %s was not found' % _format_package_id(package_id))
                    continue

    def _show_details_pkg(self, pkg):

        pkgver = _get_package_ver(pkg)
        package_id = self.get_package_id(pkg.name, pkgver, pkg.arch, pkg.repo)
        summary = _to_unicode(pkg.summary)
        desc = _to_unicode(pkg.description)
        url = _to_unicode(pkg.url)
        license = _to_unicode(pkg.license)

        # some RPM's (especially from google) have no description
        if desc:
            desc = desc.replace('\n', ';')
            desc = desc.replace('\t', ' ')
        else:
            desc = ''

        # if we are remote and in the cache, our size is zero
        size = pkg.size
        if not pkg.repo.id.startswith('installed') and pkg.verifyLocalPkg():
            size = 0

        group = self.comps.get_group(pkg.name)
        self.details(package_id, summary, license, group, desc, url, size)

    def get_files(self, package_ids):
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)

        for package_id in package_ids:
            try:
                pkg, inst = self._findPackage(package_id)
            except PkError, e:
                self.error(e.code, e.details, exit=True)
                return
            if pkg:
                files = pkg.returnFileEntries('dir')
                files.extend(pkg.returnFileEntries()) # regular files
                file_list = ";".join(files)
                self.files(package_id, file_list)
            else:
                self.error(ERROR_PACKAGE_NOT_FOUND, 'Package %s was not found' % package_id)

    def _pkg_to_id(self, pkg):
        pkgver = _get_package_ver(pkg)
        if hasattr(pkg.repo, 'id'):
            repo = pkg.repo.id
        else:
            repo = pkg.repo
        if repo.startswith('/'):
            repo = "local"
        # can we add data from the yumdb
        if repo == 'installed':
            repo_tmp = pkg.yumdb_info.get('from_repo')
            if repo_tmp:
                repo = 'installed:' + repo_tmp
        package_id = self.get_package_id(pkg.name, pkgver, pkg.arch, repo)
        return package_id

    def _show_package(self, pkg, status):
        '''  Show info about package'''
        package_id = self._pkg_to_id(pkg)
        self.package(package_id, status, pkg.summary)

    def get_distro_upgrades(self):
        '''
        Implement the get-distro-upgrades functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_QUERY)

        # if we're RHEL, then we don't have preupgrade
        if not os.path.exists('/usr/share/preupgrade/releases.list'):
            return

        # parse the releases file
        config = ConfigParser.ConfigParser()
        config.read('/usr/share/preupgrade/releases.list')

        # find the newest release
        newest = None
        last_version = 0
        for section in config.sections():
            # we only care about stable versions
            if config.has_option(section, 'stable') and config.getboolean(section, 'stable'):
                version = config.getfloat(section, 'version')
                if (version > last_version):
                    newest = section
                    last_version = version

        # got no valid data
        if not newest:
            self.error(ERROR_FAILED_CONFIG_PARSING, "could not get latest distro data")

        # are we already on the latest version
        try:
            present_version = float(self.yumbase.conf.yumvar['releasever'])
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        if (present_version >= last_version):
            return

        # if we have an upgrade candidate then pass back data to daemon
        tok = newest.split(" ")
        name = "%s-%s" % (tok[0].lower(), tok[1])
        self.distro_upgrade(DISTRO_UPGRADE_STABLE, name, newest)

    def _get_status(self, notice):
        ut = notice['type']
        if ut == 'security':
            return INFO_SECURITY
        elif ut == 'bugfix':
            return INFO_BUGFIX
        elif ut == 'enhancement':
            return INFO_ENHANCEMENT
        elif ut == 'newpackage':
            return INFO_ENHANCEMENT
        else:
            self.message(MESSAGE_BACKEND_ERROR, "status unrecognised, please report in bugzilla: %s" % ut)
            return INFO_NORMAL

    def get_updates(self, filters):
        '''
        Implement the get-updates functionality
        @param filters: package types to show
        '''
        # do not use network access if we're only returning ONLY_DOWNLOAD
        if FILTER_DOWNLOADED in filters:
            self.has_network = False
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)

        # yum 'helpfully' keeps an array of updates available
        self.yumbase.up = None

        # clear the package sack so we can get new updates
        self.yumbase.pkgSack = None

        package_list = []
        pkgfilter = YumFilter(filters)
        pkgs = []
        try:
            ygl = self.yumbase.doPackageLists(pkgnarrow='updates')
            pkgs.extend(ygl.updates)
            ygl = self.yumbase.doPackageLists(pkgnarrow='obsoletes')
            pkgs.extend(ygl.obsoletes)
        except yum.Errors.RepoError, e:
            self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # get the list of installed updates as this is needed for get_applicable_notices()
        installed_dict = {}
        for pkgtup_updated, pkgtup_installed in self.yumbase.up.getUpdatesTuples():
            installed_dict[pkgtup_installed[0]] = pkgtup_installed

        md = self.updateMetadata
        for pkg in unique(pkgs):
            if pkgfilter._filter_base(pkg):
                # we pre-get the ChangeLog data so that the changes file is
                # downloaded at GetUpdates time, not when we open the GUI
                # get each element of the ChangeLog
                try:
                    changelog = pkg.returnChangelog()
                except yum.Errors.RepoError, e:
                    self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

                # fall back to this if there is no installed update or there is no metadata
                status = INFO_NORMAL

                # Get info about package in updates info (using the installed update of this name)
                if installed_dict.has_key(pkg.name):
                    pkgtup = installed_dict[pkg.name]
                    notices = md.get_applicable_notices(pkgtup)
                    if notices:
                        for (pkgtup, notice) in notices:
                            status = self._get_status(notice)
                            if status == INFO_SECURITY:
                                break
                pkgfilter.add_custom(pkg, status)

        package_list = pkgfilter.get_package_list()
        self._show_package_list(package_list)

    def repo_enable(self, repoid, enable):
        '''
        Implement the repo-enable functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.status(STATUS_INFO)
        try:
            repo = self.yumbase.repos.getRepo(repoid)
            if not enable:
                if repo.isEnabled():
                    repo.disablePersistent()
            else:
                if not repo.isEnabled():
                    repo.enablePersistent()
                    if repoid.find ("rawhide") != -1:
                        warning = "These packages are untested and still under development." \
                                  "This repository is used for development of new releases.\n\n" \
                                  "This repository can see significant daily turnover and major " \
                                  "functionality changes which cause unexpected problems with " \
                                  "other development packages.\n" \
                                  "Please use these packages if you want to work with the " \
                                  "Fedora developers by testing these new development packages.\n\n" \
                                  "If this is not correct, please disable the %s software source." % repoid
                        self.message(MESSAGE_REPO_FOR_DEVELOPERS_ONLY, warning.replace("\n", ";"))
        except yum.Errors.RepoError, e:
            self.error(ERROR_REPO_NOT_FOUND, _to_unicode(e))
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        # update the comps groups too
        self.comps.refresh()

    def get_repo_list(self, filters):
        '''
        Implement the get-repo-list functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.status(STATUS_INFO)

        try:
            repos = self.yumbase.repos.repos.values()
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            return
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
            return
        for repo in repos:
            if not FILTER_NOT_DEVELOPMENT in filters or not _is_development_repo(repo.id):
                enabled = repo.isEnabled()
                self.repo_detail(repo.id, repo.name, enabled)

    def _get_obsoleted(self, name):
        try:
            # make sure yum doesn't explode in some internal fit of rage
            self.yumbase.up.doObsoletes()
            obsoletes = self.yumbase.up.getObsoletesTuples(newest=1)
            for (obsoleting, installed) in obsoletes:
                if obsoleting[0] == name:
                    pkg =  self.yumbase.rpmdb.searchPkgTuple(installed)[0]
                    return self._pkg_to_id(pkg)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            pass # no obsolete data - fd#17528
        return ""

    def _get_updated(self, pkg):
        try:
            pkgs = self.yumbase.rpmdb.searchNevra(name=pkg.name, arch=pkg.arch)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        if pkgs:
            return self._pkg_to_id(pkgs[0])
        else:
            return ""

    def _get_update_metadata(self):
        if not self._updateMetadata:
            self._updateMetadata = UpdateMetadata()
            for repo in self.yumbase.repos.listEnabled():
                try:
                    self._updateMetadata.add(repo)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    pass # No updateinfo.xml.gz in repo
        return self._updateMetadata

    _updateMetadata = None
    updateMetadata = property(fget=_get_update_metadata)

    def _get_update_extras(self, pkg):
        md = self.updateMetadata
        notice = md.get_notice((pkg.name, pkg.version, pkg.release))
        urls = {'bugzilla':[], 'cve' : [], 'vendor': []}
        if notice:
            # Update Details
            desc = notice['description']
            if desc:
                desc = desc.replace("\t", " ")

            # add link to bohdi if available
            if notice['from'].find('updates@fedoraproject.org') != -1:
                if notice['update_id']:
                    releasever = self.yumbase.conf.yumvar['releasever']
                    href = "https://admin.fedoraproject.org/updates/F%s/%s" % (releasever, notice['update_id'])
                    title = "%s Update %s" % (notice['release'], notice['update_id'])
                    urls['vendor'].append("%s;%s" % (href, title))

            # Update References (Bugzilla, CVE ...)
            refs = notice['references']
            if refs:
                for ref in refs:
                    typ = ref['type']
                    href = ref['href']
                    title = ref['title'] or ""

                    # Description can sometimes have ';' in them, and we use that as the delimiter
                    title = title.replace(";", ", ")

                    if href:
                        if typ in ('bugzilla', 'cve'):
                            urls[typ].append("%s;%s" % (href, title))
                        else:
                            urls['vendor'].append("%s;%s" % (href, title))

            # other interesting data:
            changelog = ''
            state = notice['status'] or ''
            issued = notice['issued'] or ''
            updated = notice['updated'] or ''
            if updated == issued:
                updated = ''

            # Reboot flag
            if notice.get_metadata().has_key('reboot_suggested') and notice['reboot_suggested']:
                reboot = 'system'
            else:
                reboot = 'none'
            return _format_str(desc), urls, reboot, changelog, state, issued, updated
        else:
            return "", urls, "none", '', '', '', ''

    def get_update_detail(self, package_ids):
        '''
        Implement the get-update_detail functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)
        for package_id in package_ids:
            try:
                pkg, inst = self._findPackage(package_id)
            except PkError, e:
                if e.code == ERROR_PACKAGE_NOT_FOUND:
                    self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, e.details)
                    continue
                self.error(e.code, e.details, exit=True)
                return
            if pkg == None:
                self.message(MESSAGE_COULD_NOT_FIND_PACKAGE, "could not find %s" % _format_package_id(package_id))
                continue
            update = self._get_updated(pkg)
            obsolete = self._get_obsoleted(pkg.name)
            desc, urls, reboot, changelog, state, issued, updated = self._get_update_extras(pkg)

            # the metadata stores broken ISO8601 formatted values
            issued = issued.replace(" ", "T")
            updated = updated.replace(" ", "T")

            # extract the changelog for the local package
            if len(changelog) == 0:

                # get the current installed version of the package
                instpkg = None
                try:
                    instpkgs = self.yumbase.rpmdb.searchNevra(name=pkg.name)
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                if len(instpkgs) == 1:
                    instpkg = instpkgs[0]

                # get each element of the ChangeLog
                try:
                    changes = pkg.returnChangelog()
                except yum.Errors.RepoError, e:
                    self.error(ERROR_REPO_NOT_AVAILABLE, _to_unicode(e))
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                for change in changes:

                    # ensure change has require number of fields
                    if len(change) != 3:
                        changelog += ";*Could not parse change element:* '%s';" % str(change)
                        continue

                    # get version number from "Seth Vidal <skvidal at fedoraproject.org> - 3:3.2.20-1"
                    header = _to_unicode(change[1])
                    version = header.rsplit(' ', 1)

                    # is older than what we have already?
                    if instpkg:
                        evr = ('0', '0', '0')
                        try:
                            evr = _getEVR(version[1])
                        except Exception, e:
                            pass
                        if evr == ('0', '0', '0'):
                            changelog += ";*Could not parse header:* '%s', *expected*: 'Firstname Lastname <email@account.com> - version-release';" % header
                        rc = rpmUtils.miscutils.compareEVR((instpkg.epoch, instpkg.version, instpkg.release.split('.')[0]), evr)
                        if rc >= 0:
                            break

                    gmtime = time.gmtime(change[0])
                    time_str = "%i-%02i-%02i" % (gmtime[0], gmtime[1], gmtime[2])
                    body = _to_unicode(change[2].replace("\t", " "))
                    changelog += _format_str('**' + time_str + '** ' + header + '\n' + body + '\n\n')

            cve_url = _format_list(urls['cve'])
            bz_url = _format_list(urls['bugzilla'])
            vendor_url = _format_list(urls['vendor'])
            self.update_detail(package_id, update, obsolete, vendor_url, bz_url, cve_url, reboot, desc, changelog, state, issued, updated)

    def repo_set_data(self, repoid, parameter, value):
        '''
        Implement the repo-set-data functionality
        '''
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        # Get the repo
        try:
            repo = self.yumbase.repos.getRepo(repoid)
        except yum.Errors.RepoError, e:
            self.error(ERROR_REPO_NOT_FOUND, "repo '%s' cannot be found in list" % repoid, exit=False)
        except exceptions.IOError, e:
            self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
        else:
            if not repo:
                self.error(ERROR_REPO_NOT_FOUND, 'repo %s not found' % repoid, exit=False)
                return
            repo.cfg.set(repoid, parameter, value)
            try:
                repo.cfg.write(file(repo.repofile, 'w'))
            except IOError, e:
                self.error(ERROR_CANNOT_WRITE_REPO_CONFIG, _to_unicode(e))

    def install_signature(self, sigtype, key_id, package_id):
        try:
            self._check_init()
        except PkError, e:
            self.error(e.code, e.details, exit=False)
            return
        self.yumbase.conf.cache = 0 # Allow new files
        self.allow_cancel(True)
        self.percentage(None)
        self.status(STATUS_INFO)
        if package_id.startswith(';;;'): #This is a repo signature
            repoid = package_id.split(';')[-1]
            repo = self.yumbase.repos.getRepo(repoid)
            if repo:
                try:
                    self.yumbase.repos.doSetup(thisrepo=repoid)
                    self.yumbase.getKeyForRepo(repo, callback = lambda x: True)
                except yum.Errors.YumBaseError, e:
                    self.error(ERROR_UNKNOWN, "cannot install signature: %s" % str(e))
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_GPG_FAILURE, "Error importing GPG Key for the %s repository: %s" % (repo, str(e)))
        else: # This is a package signature
            try:
                pkg, inst = self._findPackage(package_id)
            except PkError, e:
                self.error(e.code, e.details, exit=True)
                return
            if pkg:
                try:
                    self.yumbase.getKeyForPackage(pkg, askcb = lambda x, y, z: True)
                except yum.Errors.YumBaseError, e:
                    self.error(ERROR_UNKNOWN, "cannot install signature: %s" % str(e))
                except exceptions.IOError, e:
                    self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
                except Exception, e:
                    self.error(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))
                except:
                    self.error(ERROR_GPG_FAILURE, "Error importing GPG Key for %s" % pkg)


    def _check_init(self, lazy_cache=False):
        '''Just does the caching tweaks'''

        # this entire section can be cancelled
        self.allow_cancel(True)

        # clear previous transaction data
        self.yumbase._tsInfo = None

        # auto_close is good for ad-hoc queries, but shouldn't be used
        # when running a transaction
        self.yumbase.rpmdb.auto_close = True

        # we are working offline
        if not self.has_network:
            self.yumbase.conf.cache = 1
            for repo in self.yumbase.repos.listEnabled():
                repo.metadata_expire = -1  # never refresh

        # choose a good default if the client didn't specify a timeout
        if self.cache_age == 0:
            self.cache_age = 60 * 60 * 24  # 24 hours
        for repo in self.yumbase.repos.listEnabled():
            # is physical media
            if repo.mediaid:
                continue
            repo.metadata_expire = self.cache_age

        # disable repos that are not contactable
        for repo in self.yumbase.repos.listEnabled():
            try:
                if not repo.mediaid:
                    repo.repoXML
                else:
                    root = self.yumbase._media_find_root(repo.mediaid)
                    if not root:
                        self.yumbase.repos.disableRepo(repo.id)
                        self.message(MESSAGE_REPO_METADATA_DOWNLOAD_FAILED,
                                     "Could not contact media source '%s', so it will be disabled" % repo.id)
            except exceptions.IOError, e:
                self.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except yum.Errors.RepoError, e:
                self.yumbase.repos.disableRepo(repo.id)
                self.message(MESSAGE_REPO_METADATA_DOWNLOAD_FAILED, "Could not contact source '%s', so it will be disabled" % repo.id)

        # should we suggest yum-complete-transaction?
        unfinished = yum.misc.find_unfinished_transactions(yumlibpath=self.yumbase.conf.persistdir)
        if unfinished and not lazy_cache:
            raise PkError(ERROR_FAILED_INITIALIZATION, 'There are unfinished transactions remaining. Please run yum-complete-transaction as root.')

        # default to 100% unless method overrides
        self.yumbase.conf.throttle = "90%"

        # do not use parallel downloading
        self.yumbase.conf.async = False

    def _setup_yum(self):
        try:
            # setup Yum Config
            self.yumbase.doConfigSetup(errorlevel=-1, debuglevel=-1)
        except Exception, e:
            raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

        self.dnlCallback = DownloadCallback(self, showNames=True)
        self.yumbase.repos.setProgressBar(self.dnlCallback)

class DownloadCallback(BaseMeter):
    """ Customized version of urlgrabber.progress.BaseMeter class """
    def __init__(self, base, showNames = False):
        BaseMeter.__init__(self)
        self.base = base
        self.percent_start = 0
        self.saved_pkgs = None
        self.number_packages = 0
        self.download_package_number = 0

    def setPackages(self, new_pkgs, percent_start, percent_length):
        self.saved_pkgs = new_pkgs
        self.number_packages = float(len(self.saved_pkgs))
        self.percent_start = percent_start

    def _getPackage(self, name):

        # no name
        if not name:
            return

        # no download data
        if not self.saved_pkgs:
            return None

        # split into name, version, release
        # for yum, name is:
        #  - gnote-0.1.2-2.fc11.i586.rpm
        # and for Presto:
        #  - gnote-0.1.1-4.fc11_0.1.2-2.fc11.i586.drpm
        sections = name.rsplit('-', 2)
        if len(sections) < 3:
            return None

        # we need to search the saved packages for a match and then return the pkg
        for pkg in self.saved_pkgs:
            if sections[0] == pkg.name:
                return pkg

        # nothing matched
        return None

    def update(self, amount_read, now=None):
        BaseMeter.update(self, amount_read, now)

    def _do_start(self, now=None):
        name = self._getName()
        if not name:
            return
        self.updateProgress(name, 0.0, "", "")

    def _do_update(self, amount_read, now=None):

        fread = format_number(amount_read)
        name = self._getName()
        if self.size is None:
            # Elapsed time
            etime = self.re.elapsed_time()
            frac = 0.0
            self.updateProgress(name, frac, fread, '')
        else:
            # Remaining time
            rtime = self.re.remaining_time()
            frac = self.re.fraction_read()
            self.updateProgress(name, frac, fread, '')

    def _do_end(self, amount_read, now=None):

        total_size = format_number(amount_read)
        name = self._getName()
        if not name:
            return
        self.updateProgress(name, 1.0, total_size, '')

    def _getName(self):
        '''
        Get the name of the package being downloaded
        '''
        return self.basename

    def updateProgress(self, name, frac, fread, ftime):
        '''
         Update the progressbar (Overload in child class)
        @param name: filename
        @param frac: Progress fracment (0 -> 1)
        @param fread: formated string containing BytesRead
        @param ftime: formated string containing remaining or elapsed time
        '''

        val = int(frac*100)

        # new package
        if val == 0 and name:
            pkg = self._getPackage(name)
            if pkg: # show package to download
                self.base._show_package(pkg, INFO_DOWNLOADING)
            else:
                for key in MetaDataMap.keys():
                    if key in name:
                        typ = MetaDataMap[key]
                        self.base.status(typ)
                        break

        # set item percentage
        if name:
            pkg = self._getPackage(name)
            if pkg:
                self.base.item_progress(self.base._pkg_to_id(pkg), STATUS_DOWNLOAD, val)

        # package finished
        if val == 100 and name:
            pkg = self._getPackage(name)
            if pkg:
                self.base._show_package(pkg, INFO_FINISHED)

        # refine percentage with subpercentage
        pct_start = StatusPercentageMap[STATUS_DOWNLOAD]
        pct_end = StatusPercentageMap[STATUS_SIG_CHECK]

        if self.number_packages > 0:
            div = (pct_end - pct_start) / self.number_packages
            pct = pct_start + (div * self.download_package_number) + ((div / 100.0) * val)
            self.base.percentage(pct)

        # keep track of how many we downloaded
        if val == 100:
            self.download_package_number += 1

class PackageKitCallback(RPMBaseCallback):
    def __init__(self, base):
        RPMBaseCallback.__init__(self)
        self.base = base
        self.curpkg = None
        self.percent_start = 0
        self.percent_length = 0

    def _showName(self, status):
        # curpkg is a yum package object or simple string of the package name
        if type(self.curpkg) in types.StringTypes:
            package_id = self.base.get_package_id(self.curpkg, '', '', '')
            # we don't know the summary text
            self.base.package(package_id, status, "")
        else:
            # local file shouldn't put the path in the package_id
            repo_id = _to_unicode(self.curpkg.repo.id)
            if repo_id.find("/") != -1:
                repo_id = 'local'

            pkgver = _get_package_ver(self.curpkg)
            package_id = self.base.get_package_id(self.curpkg.name, pkgver, self.curpkg.arch, repo_id)
            self.base.package(package_id, status, self.curpkg.summary)

    def event(self, package, action, te_current, te_total, ts_current, ts_total):

        if str(package) != str(self.curpkg):
            self.curpkg = package
            try:
                self.base.status(TransactionsStateMap[action])
                self._showName(TransactionsInfoMap[action])
            except exceptions.IOError, e:
                self.base.error(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
            except exceptions.KeyError, e:
                self.base.message(MESSAGE_BACKEND_ERROR, "The constant '%s' was unknown, please report. details: %s" % (action, _to_unicode(e)))

        # set item percentage
        if package and te_total > 0:
            val = (te_current*100L) / te_total
            if self.curpkg:
                # curpkg is a yum package object or simple string of the package name
                if type(self.curpkg) in types.StringTypes:
                    package_id = self.base.get_package_id(self.curpkg, '', '', '')
                    self.base.item_progress(package_id, TransactionsStateMap[action], val)
                else:
                    repo_id = _to_unicode(self.curpkg.repo.id)
                    if repo_id.find("/") != -1:
                        repo_id = 'local'
                    pkgver = _get_package_ver(self.curpkg)
                    package_id = self.base.get_package_id(self.curpkg.name, pkgver, self.curpkg.arch, repo_id)
                    self.base.item_progress(package_id, TransactionsStateMap[action], val)

        # find out the offset
        pct_start = StatusPercentageMap[STATUS_INSTALL]

        # do percentage
        if ts_total > 0 and te_total > 0:
            val = (te_current * 100L) / te_total
            div = (100 - pct_start) / ts_total
            pct = div * (ts_current - 1) + pct_start + ((div / 100.0) * val)
            self.base.percentage(pct)

    def errorlog(self, msg):
        # grrrrrrrr
        pass

class ProcessTransPackageKitCallback:
    def __init__(self, base):
        self.base = base

    def event(self, state, data=None):

        if state == PT_DOWNLOAD:        # Start Downloading
            self.base.allow_cancel(True)
            pct_start = StatusPercentageMap[STATUS_DOWNLOAD]
            self.base.percentage(pct_start)
            self.base.status(STATUS_DOWNLOAD)
        elif state == PT_DOWNLOAD_PKGS:   # Packages to download
            self.base.dnlCallback.setPackages(data, 10, 30)
        elif state == PT_GPGCHECK:
            pct_start = StatusPercentageMap[STATUS_SIG_CHECK]
            self.base.percentage(pct_start)
            self.base.status(STATUS_SIG_CHECK)
        elif state == PT_TEST_TRANS:
            pct_start = StatusPercentageMap[STATUS_TEST_COMMIT]
            self.base.allow_cancel(False)
            self.base.percentage(pct_start)
            self.base.status(STATUS_TEST_COMMIT)
        elif state == PT_TRANSACTION:
            pct_start = StatusPercentageMap[STATUS_INSTALL]
            self.base.allow_cancel(False)
            self.base.percentage(pct_start)
        else:
            self.base.message(MESSAGE_BACKEND_ERROR, "unhandled transaction state: %s" % state)

class DepSolveCallback(object):

    # takes a PackageKitBackend so we can call StatusChanged on it.
    # That's kind of hurky.
    def __init__(self, backend):
        self.started = False
        self.backend = backend

    def start(self):
        if not self.started:
            self.backend.status(STATUS_DEP_RESOLVE)
            pct_start = StatusPercentageMap[STATUS_DEP_RESOLVE]
            self.backend.percentage(pct_start)

    # Be lazy and not define the others explicitly
    def _do_nothing(self, *args, **kwargs):
        pass

    def __getattr__(self, x):
        return self._do_nothing

class PackageKitYumBase(yum.YumBase):
    """
    Subclass of YumBase.  Needed so we can overload _checkSignatures
    and nab the gpg sig data
    """

    def __init__(self, backend):
        yum.YumBase.__init__(self)

        # YumBase.run_with_package_names is used to record which packages are
        # involved in executing a transaction
        if hasattr(self, 'run_with_package_names'):
            self.run_with_package_names.add('PackageKit-yum')

        # load the config file
        config = ConfigParser.ConfigParser()
        try:
            config.read('/etc/PackageKit/Yum.conf')
            disabled_plugins = config.get('Backend', 'DisabledPlugins').split(';')
        except ConfigParser.NoOptionError, e:
            disabled_plugins = []
        except Exception, e:
            raise PkError(ERROR_REPO_CONFIGURATION_ERROR, "Failed to load Yum.conf: %s" % _to_unicode(e))
        disabled_plugins.append('refresh-packagekit')

        # disable the PackageKit plugin when running under PackageKit
        try:
            pc = self.preconf
            pc.disabled_plugins = disabled_plugins
        except yum.Errors.ConfigError, e:
            raise PkError(ERROR_REPO_CONFIGURATION_ERROR, _to_unicode(e))
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except ValueError, e:
            raise PkError(ERROR_FAILED_CONFIG_PARSING, _to_unicode(e))

        # setup to use LANG for descriptions
        yum.misc.setup_locale(override_time=True, override_codecs=False)

        self.missingGPGKey = None
        self.dsCallback = DepSolveCallback(backend)
        self.backend = backend
        self.mediagrabber = self.MediaGrabber

        # Enable new callback mode on yum versions that support it
        self.use_txmbr_in_callback = True

        # setup Repo GPG support callbacks
        #
        # self.preconf may or may not exist at this point...
        # What we think is happening is that it's going:
        #
        #    PK.init => yum._getRepos => yum.conf =>
        #    plugins *.init_hook => RHN.init => conduit.getRepos() =>
        #    yum._getRepos
        #
        # ..at which point we try to setup from prerepoconf twice, and
        # the second time it fails.
        try:
            if hasattr(self, 'prerepoconf'):
                self.prerepoconf.confirm_func = self._repo_gpg_confirm
                self.prerepoconf.gpg_import_func = self._repo_gpg_import
            else:
                self.repos.confirm_func = self._repo_gpg_confirm
                self.repos.gpg_import_func = self._repo_gpg_import
        except exceptions.IOError, e:
            raise PkError(ERROR_NO_SPACE_ON_DEVICE, "Disk error: %s" % _to_unicode(e))
        except Exception, e:
            # helpfully, yum gives us TypeError when it can't open the rpmdb
            if str(e).find('rpmdb open failed') != -1:
                raise PkError(ERROR_FAILED_INITIALIZATION, _format_str(traceback.format_exc()))
            else:
                raise PkError(ERROR_INTERNAL_ERROR, _format_str(traceback.format_exc()))

    def _media_find_root(self, media_id):
        """ returns the root "/media/Fedora Extras" or None """

        # search all the disks
        vm = gio.volume_monitor_get()
        mounts = vm.get_mounts()
        for mount in mounts:
            # is it mounted
            root = mount.get_root().get_path()

            # is it a media disc
            discinfo = "%s/.discinfo" % root
            if not os.path.exists(discinfo):
                continue

            # get the contents
            f = open(discinfo, "r")
            lines = f.readlines()
            f.close()

            # not enough lines to be a valid .discinfo
            if len(lines) < 3:
                continue

            # check this is the right disk
            media_id_tmp = lines[0].strip()
            if cmp(media_id_tmp, media_id) != 0:
                continue

            return root

        # nothing remaining
        return None

    def MediaGrabber(self, *args, **kwargs):
        """
        Handle physical media.
        """
        root = self._media_find_root(kwargs["mediaid"])
        if root:
            # the actual copying is done by URLGrabber
            ug = URLGrabber(checkfunc = kwargs["checkfunc"])
            try:
                ug.urlgrab("%s/%s" % (root, kwargs["relative"]),
                           kwargs["local"], text=kwargs["text"],
                           range=kwargs["range"], copy_local=1)
            except (IOError, URLGrabError), e:
                pass

        # we have to send a message to the client
        if not root:
            name = "%s Volume #%s" % (kwargs["name"], kwargs["discnum"])
            self.backend.media_change_required(MEDIA_TYPE_DISC, name, name)
            self.backend.error(ERROR_MEDIA_CHANGE_REQUIRED,
                               "Insert media labeled '%s' or disable media repos" % name,
                               exit=False)
            raise yum.Errors.MediaError, "The disc was not inserted"

        # yay
        return kwargs["local"]

    def _repo_gpg_confirm(self, keyData):
        """ Confirm Repo GPG signature import """
        if not keyData:
            self.backend.error(ERROR_BAD_GPG_SIGNATURE,
                       "GPG key not imported, and no GPG information was found.")
        repo = keyData['repo']
        fingerprint = keyData['fingerprint']()
        hex_fingerprint = "%02x" * len(fingerprint) % tuple(map(ord, fingerprint))
        # Borrowed from http://mail.python.org/pipermail/python-list/2000-September/053490.html

        self.backend.repo_signature_required(";;;%s" % repo.id,
                                     repo.id,
                                     keyData['keyurl'].replace("file://", ""),
                                     keyData['userid'],
                                     keyData['hexkeyid'],
                                     hex_fingerprint,
                                     time.ctime(keyData['timestamp']),
                                     'gpg')
        self.backend.error(ERROR_GPG_FAILURE, "GPG key %s required" % keyData['hexkeyid'])

    def _repo_gpg_import(self, repo, confirm):
        """ Repo GPG signature importer"""
        self.getKeyForRepo(repo, callback=confirm)

    def _checkSignatures(self, pkgs, callback):
        ''' The the signatures of the downloaded packages '''
        # This can be overloaded by a subclass.

        for po in pkgs:
            result, errmsg = self.sigCheckPkg(po)
            if result == 0:
                # verified ok, or verify not required
                continue
            elif result == 1:
                # verify failed but installation of the correct GPG key might help
                self.getKeyForPackage(po, fullaskcb=self._fullAskForGPGKeyImport)
            else:
                # fatal GPG verification error
                raise yum.Errors.YumGPGCheckError, errmsg
        return 0

    def _fullAskForGPGKeyImport(self, data):
        self.missingGPGKey = data

        raise GPGKeyNotImported()

    def _askForGPGKeyImport(self, po, userid, hexkeyid):
        '''
        Ask for GPGKeyImport
        '''
        # TODO: Add code here to send the RepoSignatureRequired signal
        return False

def main():
    backend = PackageKitYumBackend('', lock=True)
    backend.dispatcher(sys.argv[1:])

if __name__ == "__main__":
    main()
