#!/usr/bin/python -tt
#    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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# (C) 2009 - Tim Lauridsen <timlau@fedoraproject.org>

'''
'''

import os
import os.path
import sys
sys.path.insert(0, '/usr/share/yum-cli')

import yum
import traceback
from optparse import OptionParser
import types
import time

from datetime import datetime

from yum.packageSack import packagesNewestByNameArch
from yum.update_md import UpdateMetadata

from yumexbase.constants import *
from yumexbackend import YumHistoryTransaction, YumHistoryPackage, pack, unpack
from yumexbase import YumexBackendFatalError


from urlgrabber.progress import format_number
import yum.logginglevels as logginglevels
import yum.Errors as Errors
from yum.rpmtrans import RPMBaseCallback
from yum.packages import YumLocalPackage
from yum.packageSack import packagesNewestByName
# Pylint in F10 cant handle the init_hook, so disable the cant find output error
# pylint: disable-msg=F0401
from output import DepSolveProgressCallBack # yum cli output.py
# pylint: enable-msg=F0401
from yum.constants import *
from yum.callbacks import *
import yumexbase.constants as const
import yum.plugins
from urlgrabber.grabber import URLGrabber, URLGrabError
from rpmUtils.arch import canCoinstall

from yum.i18n import _ as yum_translated


# We want these lines, but don't want pylint to whine about the imports not being used
# pylint: disable-msg=W0611
from yumexbase import _, P_  # lint:ok
# pylint: enable-msg=W0611

def catchYumException(func):
    """
    This decorator catch yum exceptions and send fatal signal to frontend
    """
    def newFunc(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except Errors.RepoError, e:
            base = args[0]
            # signal to the front end that we have a fatal error
            base.fatal('repo-error', str(e))
            base.ended(False)

    newFunc.__name__ = func.__name__
    newFunc.__doc__ = func.__doc__
    newFunc.__dict__.update(func.__dict__)
    return newFunc

class _YumPreBaseConf:
    """This is the configuration interface for the YumBase configuration.
       So if you want to change if plugins are on/off, or debuglevel/etc.
       you tweak it here, and when yb.conf does it's thing ... it happens. """

    def __init__(self):
        self.fn = '/etc/yum/yum.conf'
        self.root = '/'
        self.init_plugins = True
        self.plugin_types = (yum.plugins.TYPE_CORE,)
        self.optparser = None
        self.debuglevel = None
        self.errorlevel = None
        self.disabled_plugins = None
        self.enabled_plugins = None
        self.syslog_ident = None
        self.syslog_facility = None
        self.syslog_device = '/dev/log'
        self.localpkg_gpgcheck = False
        
class YumexYumBase(yum.YumBase):
    """
    We need a custom YumBase class, so we can overload the _askForGPGKeyImport method
    so we can ask the user for import of a missing GPG key.
    """
    
    def __init__(self, base):
        yum.YumBase.__init__(self)
        self.base = base
        
    def _askForGPGKeyImport(self, po, userid, hexkeyid):
        '''
        Ask for GPGKeyImport
        
        call _askForGPGKeyImport in the parent class
        '''
        return self.base._askForGPGKeyImport( po, userid, hexkeyid)
        

class YumServer:
    """
    A yum server class to be used in a spawned process.
    it receives commands from stdin and send results and info
    to stdout.

    Commands: (commands and parameters are separated with '\t' )
        get-packages <pkg-filter> <show_dupes> <disable_cache> : get a list of packages based on a filter
        get-packages-size                    :
        get-packages-repo                    :
        get-attribute <pkg_id> <attribute>   : get an attribute of an package
        get-changelog                        :
        update-info                          :
        set-option                           :
        add-transaction <pkg_id> <action>    : add a po to the transaction
        remove-transaction <pkg_id>          : add a po to the transaction
        list-transaction                     : list po's in transaction
        build-transaction                    : build the transaction (resolve dependencies)
        run-transaction                      : run the transaction
        get-groups                           : Get the groups
        get-group-packages                   :
        get-repos                            : Get the repositories
        enable-repo                          : enable/disable a repository
        enable-repo-persistent               :
        search                               : search
        clean                                :
        get-history                          :
        get-history-packages                 :
        history-undo                         :
        history-redo                         :

        Parameters:
        <pkg-filter> : all,installed,available,updates,obsoletes
        <pkg_id>     : name epoch ver release arch repoid ('\t' separated)
        <attribute>  : pkg attribute (ex. description, changelog)
        <action>     : 'install', 'update', 'remove'
        <show_dupes> : Show duplicate packages (True or False)

    Results:(starts with and ':' and cmd and parameters are separated with '\t')

        :info <message>        : information message
        :error <message>       : error message
        :warning <message>     : warning message
        :debug <message>       : debug message
        :exception <message>   : exception message
        :yum <message>         : yum logger message
        :pkg <pkg>             : package
        :end                   : end of package list command
        :attr <object>         : package object attribute
        :group <grp>           : group
        :repo <repo>           : repo
        :msg <type> <value>
        :media-change <media info>

        Parameters:
        <message>  : a text message ('\n' is replaced with ';'
        <pkg>      : name epoch ver release arch repoid summary ('\t' separated)
        <object>   : an package attribute pickled and base64 encoded.


    """

    def __init__(self, debuglevel=2, plugins=True, offline=False, enabled_repos=None, yum_conf='/etc/yum.conf'):
        '''  Setup the spawned server '''
        print('Yum Version : %s' % yum.__version__)
        self._yumbase = None
        if yum_conf != '/etc/yum.conf':
            self.info(_('Using %s for yum configuration') % yum_conf)
        self.yum_conf = yum_conf
        self.enabled_repos = enabled_repos
        logginglevels.setLoggingApp('yumex')
        self.dnlCallback = YumexDownloadCallback(self)
        self.offline = offline
        self.debuglevel = debuglevel
        self.plugins = plugins
        self._updateMetadata = None # Update metadata cache
        self._updates_list = None
        self._obsoletes_list = None
        self._last_search = None
        self._last_search_result = None
        self._package_cache = {}
        self._last_yumbase_reload = int(time.time())
        self._applied_set_options = {}
        self.yumbase # init the YumBase attribute
        self.write(':started') # Let the front end know that we are up and running

    @property
    def yumbase(self):
        if not self._yumbase:
            yumbase = YumexYumBase(self)
            self._yumbase = yumbase
            yumbase.mediagrabber = self.mediaGrabber
            parser = OptionParser()
            # FIXME: workaround for issue in the auto-update-debuginfo plugin
            # The issue is fixed in upstream yum-utils, but it make take some time to get it out.
            # So we work around it here
            parser.add_option("--enablerepo", type='string', dest='repos', default=[])
            # Setup yum preconfig options
            yumbase.preconf.debuglevel = self.debuglevel
            yumbase.preconf.init_plugins = self.plugins
            yumbase.preconf.optparser = parser
            if hasattr(yumbase, 'run_with_package_names'):
                yumbase.run_with_package_names.add("yumex")
            yumbase.preconf.fn = self.yum_conf
            # Disable refresh-package plugin, it will get in the way every time we finish a transaction
            yumbase.preconf.disabled_plugins = ['refresh-packagekit']
            yumbase.setCacheDir()
            yumbase.repos.setProgressBar(self.dnlCallback)
            # make some dummy options,args for yum plugins
            (options, args) = parser.parse_args()
            yumbase.plugins.setCmdLine(options, args)
            yumbase.dsCallback = DepSolveProgressCallBack(ayum=yumbase)
            # Setup repos
            self._setup_repos(self.enabled_repos)
            self._disable_multi_download() # disable parallel file download
            # Setup failure callback
            freport = (self._failureReport, (), {})
            yumbase.repos.setFailureCallback(freport)
        return self._yumbase

    def _is_local_repo(self, repo):
        '''
        Check if an repo is local (media or file:// repo)
        '''
        if repo.mediaid or self._is_file_url(repo.baseurl):
            return True
        else:
            return False

    def _is_file_url(self, urls):
        return [url for url in urls if url.startswith('file:')]

    def _disable_multi_download(self):
        # Disable parallel down for this repo, we dont support it
        for repo in self.yumbase.repos.listEnabled():
            self.debug("disable multi : "+repo.id)
            repo._async = False


    def _setup_repos(self, enabled_repos):
        '''
        Setup the repo to be enabled/disabled
        '''
        if not self.offline: # Online
            if enabled_repos: # Use the positive list of repos to enable
                for repo in self.yumbase.repos.repos.values():
                    if repo.id in enabled_repos: # is in the positive list
                        self.yumbase.repos.enableRepo(repo.id)
                    else:
                        self.yumbase.repos.disableRepo(repo.id)
        else: # Offline, use only media or locale file:// repos
            if enabled_repos: # Use the supplied list of repos to enable
                for repo in self.yumbase.repos.repos.values():
                    if repo.id in enabled_repos:
                        if self._is_local_repo(repo): # is local ?
                            self.yumbase.repos.enableRepo(repo.id)
                        else: # Not local disable it
                            self.info(_("No network connection, disable non local repo %s") % repo.id)
                            self.yumbase.repos.disableRepo(repo.id)
                    else: # not in positive list, disable it
                        self.yumbase.repos.disableRepo(repo.id)
            else: # Use the default enabled ones
                for repo in self.yumbase.repos.listEnabled():
                    if self._is_local_repo(repo): # is local ?
                        self.yumbase.repos.enableRepo(repo.id)
                    else: # No, disable it
                        self.info(_("No network connection, disable non local repo %s") % repo.id)
                        self.yumbase.repos.disableRepo(repo.id)

    def doLock(self, lockfile=YUM_PID_FILE):
        '''
        Active the yum lock.
        @param lockfile: path to yum lock file
        '''
        nmsg = ""
        first_time=True
        while True:
            try:
                self.yumbase.doLock()
                if not first_time:
                    self.lock_msg('got-lock', None)
                return True
            except Errors.LockError, e:
                self.warning(_("Yum is locked : ") + e.msg)
                if hasattr(e, 'pid'):
                    ps = self.get_process_info(e.pid)
                    if ps:
                        if ps['name'] == 'yumBackend.py':
                            nmsg = _("  The other application is: PackageKit")
                        else:
                            nmsg = _("  The other application is: %s") % ps['name']
                        self.warning(nmsg)
                if first_time:
                    first_time=False
                    self.lock_msg('try-lock', [e.msg, nmsg])
                self.warning(_("Waiting 10 seconds and tries again !!!"))
                time.sleep(10)

    def doUnlock(self):
        self.yumbase.doUnlock()

    def mediaGrabber(self, *args, **kwargs):
        '''
        Media handler
        '''
        mediaid = kwargs["mediaid"]
        discnum = kwargs["discnum"]
        name = kwargs["name"]
        found = False
        prompt_first = False # check without asking the user
        while(not found):
            mp = self._ask_for_media_change(prompt_first, mediaid, name, discnum)
            # We got the media mount point
            if mp:
                # the actual copying is done by URLGrabber
                ug = URLGrabber(checkfunc=kwargs["checkfunc"])
                try:
                    ug.urlgrab("%s/%s" % (mp, kwargs["relative"]),
                        kwargs["local"], text=kwargs["text"],
                        range=kwargs["range"], copy_local=1)
                except (IOError, URLGrabError):
                    # ask again as user might got a better media or he might clean the media
                    prompt_first = True # next time prompt user first so he can cancel
                else:
                    found = True # done
            else:
                # mp==None ie. user canceled media change
                break
        if not found:
            # yumRepo will catch this
            raise yum.Errors.MediaError, _("The disc was not inserted")
        return kwargs["local"]

    def get_process_info(self, pid):
        '''
        Get process information from /proc
        @param pid: the process id
        '''
        if not pid:
            return None

        # Maybe true if /proc isn't mounted, or not Linux ... or something.
        if (not os.path.exists("/proc/%d/status" % pid) or
            not os.path.exists("/proc/stat") or
            not os.path.exists("/proc/%d/stat" % pid)):
            return None

        ps = {}
        for line in open("/proc/%d/status" % pid):
            if line[ -1] != '\n':
                continue
            data = line[:-1].split(':\t', 1)
            if data[1].endswith(' kB'):
                data[1] = data[1][:-3]
            ps[data[0].strip().lower()] = data[1].strip()
        return ps

    def quit(self, returncode=0):
        '''
        Exit the yum backend
        '''
        self.yumbase.closeRpmDB()
        if returncode == 0: # only write on normal exit
            self.info(_("Closing rpm db and releasing yum lock  "))
            self.ended(True)
        sys.exit(returncode)

    def write(self, msg):
        ''' write an message to stdout, to be read by the client'''
        try:
            if msg == "die":
                f = open('/does/not/exist', 'r')
            msg.replace("\n", ";")
            sys.stdout.write("%s\n" % msg)
            # just a test killer to generate an io error and check it is handled correct
        except:
            # stdout is broken, we can't do much to tell the frontend
            # so write the exception to an dump file and quit the backend
            try:
                errmsg = traceback.format_exc()
                ct = datetime.now().strftime("%Y%m%d-%H%M") # get YYYYMMDD-hhmm
                dumpfile = "/usr/tmp/yumex-dump-%s.txt" % ct
                fh = open(dumpfile, "w")
                print >> fh, "Exception in write : %s\n\n" % msg
                print >> fh, errmsg
                fh.close()
            except:
                pass
            finally:
                sys.exit(5) # end with error code 5


    def _get_recent(self, po):
        '''
        get the recent state of a package
        @param po: yum package object
        '''
        if po.repoid == 'installed':
            ftime = int(po.returnSimple('installtime'))
        else:
            ftime = int(po.returnSimple('filetime'))
        if ftime > RECENT_LIMIT:
            return 1
        else:
            return 0

    def _is_installed(self, po):
        '''
        Check if a package is installed
        @param po: package to check for
        '''
        (n, a, e, v, r) = po.pkgtup
        po = self.yumbase.rpmdb.searchNevra(name=n, arch=a, ver=v, rel=r, epoch=e)
        if po:
            return True
        else:
            return False


    def _show_history_package(self, pkg):
        ''' write history package result'''
        if not hasattr(pkg, 'state_installed'):
            pkg.state_installed = self._is_installed(pkg)
        yhp = pack(YumHistoryPackage(pkg))
        self.write(":histpkg\t%s" % yhp)

    def _show_history_item(self, yht):
        ''' write package result'''
        item = pack(YumHistoryTransaction(yht))
        self.write(":hist\t%s" % item)

    def _get_package_id(self, pkg, action=None):
        summary = pack(pkg.summary)
        recent = self._get_recent(pkg)
        if not action:
            pkg, action = self._get_action(pkg)
        action = pack(action)
        if hasattr(pkg,'pkgtype') and pkg.pkgtype == 'local': # if local package, then return localpath instead of repoid
            return (pkg.name, pkg.epoch, pkg.ver, pkg.rel, pkg.arch, pkg.localpath,
                    summary, action, pkg.size, recent)
        else:
            return (pkg.name, pkg.epoch, pkg.ver, pkg.rel, pkg.arch, pkg.ui_from_repo,
                    summary, action, pkg.size, recent)

    def _show_requirement(self, req, pkg):
        ''' write history package result'''
        tup = pack((req, self._get_package_id(pkg)))
        self.write(":req\t%s" % tup)

    def _show_package(self, pkg, action=None):
        ''' write history package result'''
        self.write(":pkg\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s" % self._get_package_id(pkg, action))

    def _show_group(self, grp):
        '''
        send a group message to the frontend
        @param grp: group object
        '''
        self.write(":group\t%s\t%s\t%s" % (grp.cat, grp.id, grp.name))

    def _show_repo(self, repo):
        '''
        send a repo message to the frontend
        @param repo: repo object
        '''
        self.write(":repo\t%s\t%s\t%s\t%s" % (repo.enabled, repo.id, repo.name, repo.gpgcheck))

    def info(self, msg):
        ''' write an info message '''
        self.write(":info\t%s" % msg)

    def error(self, msg):
        ''' write an error message '''
        self.write(":error\t%s" % msg)

    def debug(self, msg, name=None):
        ''' write an debug message '''
        if not name:
            classname = __name__.split('.')[-1]
            name = classname + "." + sys._getframe(1).f_code.co_name
        self.write(":debug\t%s\t%s" % (msg, name))

    def warning(self, msg):
        ''' write an warning message '''
        self.write(":warning\t%s" % msg)

    def fatal(self, err, msg, raise_exception = True):
        ''' write an fatal message '''
        pmsg = pack(msg)
        self.write(":fatal\t%s\t%s" % (err, pmsg))
        if raise_exception:
            raise YumexBackendFatalError(err, msg)

    def gpg_check(self, po, userid, hexkeyid):
        ''' write an fatal message '''
        value = (str(po), userid, hexkeyid)
        value = pack(value)
        self.write(":gpg-check\t%s" % (value))

    def media_change(self, prompt_first, media_id, media_name, media_num):
        ''' write an media change message '''
        value = (prompt_first, media_id, media_name, media_num)
        value = pack(value)
        self.write(":media-change\t%s" % (value))


    def message(self, msg_type, value):
        '''
        send at a custom message to the frontend
        @param msg_type: message type
        @param value: some standard python object (dict,list,tuble etc.) to pass with the message.
        '''
        value = pack(value)
        self.write(":msg\t%s\t%s" % (msg_type, value))

    def lock_msg(self, state, additional):
        '''
        send a lock-specific message to the frontend
        '''
        value = pack(additional)
        self.write(":lock\t%s\t%s" % (state, value))

    def yum_rpm(self, action, package, frac, ts_current, ts_total):
        ''' write an yum rpm action progressmessage '''
        value = (action, package, frac, ts_current, ts_total)
        value = pack(value)
        self.write(":yum-rpm\t%s" % value)

    def yum_dnl(self, ftype, name, percent, cur, tot, fread, ftotal, ftime):
        '''
        send an yum download progress message to the frontend
        @param ftype: filetype ('PKG' or 'REPO')
        @param name: filename
        @param percent: percent complette
        @param cur: current number of file being downloaded
        @param tot: total number of files to download
        @param fread: formatted number of bytes read
        @param ftime: formatted ETA
        '''
        value = (ftype, name, percent, cur, tot, fread, ftotal, ftime)
        value = pack(value)
        self.write(":yum-dnl\t%s" % value)

    def yum_state(self, state):
        '''
        Send an yum transaction state message
        @param state:
        '''
        self.write(":yum-state\t%s" % state)

    def yum_logger(self, msg):
        ''' write an yum logger message '''
        msg = pack(msg)
        self.write(":yum\t%s" % msg)

    def ended(self, state):
        '''
        Send and ended message
        used to signal the end of a process
        @param state: the exit state of the process (True/False)
        '''
        state = pack(state)
        self.write(":end\t%s" % state)

    def _reload_yumbase(self, force=False):
        """
        recreate the yumbase object. this forces to recreate all cached lists in
        YumBase. to avoid frequent recreation, a minimum time interval is used.

        it should be safe to call this in a locked state, since the lock is a
        persistent file (YumBase stores no additional locking info)

        @param force: force recreating the object
        """
        time_now = int(time.time())
        if time_now - self._last_yumbase_reload > 60 or force: # min reload interval
            self._last_yumbase_reload = time_now
            self.enabled_repos = map(lambda r: r.id, self.yumbase.repos.listEnabled())
            self._yumbase = None # force reinitialization of YumBase
            # restore the set-option commands
            for k,v in self._applied_set_options.iteritems():
                self._set_option(k, v)

            return True
        return False


    #@catchYumException
    def get_packages(self, narrow, dupes, disable_cache):
        '''
        get list of packages and send results
        @param narrow:
        '''
        if narrow:
            show_dupes = (dupes == 'True')
            if not narrow in self._package_cache or disable_cache == 'True':
                if disable_cache == 'True': #download newest repo data
                    if self._reload_yumbase():
                        self.yumbase.cleanExpireCache() # force searching for updates

                self.info(PACKAGE_LOAD_MSG[narrow])
                ygh = self.yumbase.doPackageLists(pkgnarrow=narrow, showdups=show_dupes)
                self._package_cache[narrow] = getattr(ygh, narrow)
            action = const.FILTER_ACTIONS[narrow]
            for pkg in self._package_cache[narrow]:
                self._show_package(pkg, action)
        self.ended(True)

    def clear_package_cache(self):
        self._package_cache = {}

    def get_packages_size(self, ndx):
        '''
        get list of packages in a size range
        @param ndx: Size range index
        '''
        ygh = self.yumbase.doPackageLists()
        action = const.FILTER_ACTIONS['available']
        for pkg in ygh.available:
            if self._in_size_range(pkg, ndx):
                self._show_package(pkg, action)
        action = const.FILTER_ACTIONS['installed']
        for pkg in ygh.installed:
            if self._in_size_range(pkg, ndx):
                self._show_package(pkg, action)
        del ygh
        self.ended(True)

    def get_available_by_name(self, name):
        pkgs = self.yumbase.pkgSack.returnPackages(patterns=[name], ignore_case=False)
        self._return_packages(pkgs)
        self.ended(True)

    def _is_valid_downgrade(self, po, down_po):
        '''
        Check if down_po is a valid downgrade to po
        @param po:
        @param down_po:
        '''
        valid = True
        if not po.verGT(down_po):   # po must be > down_po
            valid = False
        elif canCoinstall(po.arch, down_po.arch): # po must not be coinstallable with down_po
            valid = False
        elif self.yumbase.allowedMultipleInstalls(po): # po must not be a multiple installable (ex. kernels )
            valid = False
        return valid

    def get_available_downgrades(self, pkgstr):
        '''
        Return valid available downgrades for a given package id
        @param pkgstr: package id
        '''
        pkg = self._getPackage(pkgstr)
        pkgs = []
        if self._is_installed(pkg): # is installed , we must find available downgrade
            apkgs = self.yumbase.pkgSack.returnPackages(patterns=[pkg.name], ignore_case=False)
            for po in apkgs:
                if self._is_valid_downgrade(pkg, po):
                    pkgs.append(po)
        else: # Not installed, this is the package to downgrade to, find the installed one
            ipkgs = self.yumbase.rpmdb.searchNevra(name=pkg.name, arch = pkg.arch)
            if ipkgs:
                pkgs.append(ipkgs[0])
        self._return_packages(pkgs)
        self.ended(True)


    def _in_size_range(self, pkg, ndx):
        min, max = SIZE_RANGES[ndx]
        if pkg.size >= min and pkg.size < max:
            return True
        else:
            return False

    def get_packages_repo(self, repoid):
        '''
        get list of packages in a size range
        @param ndx: Size range index
        '''
        ygh = self.yumbase.doPackageLists('installed')
        action = const.FILTER_ACTIONS['available']
        pkgs = self.yumbase.pkgSack.returnPackages(repoid=repoid)
        for pkg in pkgs:
            if not self._is_installed(pkg):
                self._show_package(pkg, action)
        action = const.FILTER_ACTIONS['installed']
        ipkgs = self.yumbase.rpmdb.returnPackages()
        for pkg in ipkgs:
            if self._in_a_repo(pkg, repoid, inst=True):
                self._show_package(pkg, action)
        del ygh
        self.ended(True)

    def _in_a_repo(self, pkg, repoid, inst=False):
        if not inst and pkg.repoid == repoid:
            return True
        elif hasattr(pkg, 'yumdb_info') and hasattr(pkg.yumdb_info, 'from_repo'):
            if pkg.yumdb_info.from_repo == repoid:
                return True
            else:
                return False
        else:
            return False

    def _getPackage(self, para):
        ''' find the real package from an package id'''
        n, e, v, r, a, ident = para
        if ident == 'installed' or ident.startswith('@'):
            pkgs = self.yumbase.rpmdb.searchNevra(n, e, v, r, a)
        else:
            repo = self.yumbase.repos.getRepo(ident) # Used the repo sack, it will be faster
            if repo:
                pkgs = repo.sack.searchNevra(n, e, v, r, a)
            else: # fallback to the use the pkgSack, just in case
                pkgs = self.yumbase.pkgSack.searchNevra(n, e, v, r, a)
        if pkgs:
            return pkgs[0]
        else:
            return None

    def get_attribute(self, args):
        ''' get a package attribute and send the result '''
        pkgstr = args[:-1]
        attr = args[ -1]
        po = self._getPackage(pkgstr)
        res = pack(None)
        if po:
            res = getattr(po, attr)
            res = pack(res)
        self.write(':attr\t%s' % res)

    def get_changelog(self, args):
        ''' get a given number of changelog lines '''
        pkgstr = args[:-1]
        num = int(args[ -1])
        po = self._getPackage(pkgstr)
        res = []
        if po:
            clog = getattr(po, 'changelog')
            if num != 0:
                i = 0
                for (c_date, c_ver, msg) in po.changelog:
                    i += 1
                    elem = (c_date, c_ver, msg)
                    res.append(elem)
                    if i == num:
                        break
            else:
                res = clog
            res = pack(res)
        self.write(':attr\t%s' % res)

    def get_dependencies(self, args):
        po = self._getPackage(args)
        deps = self.yumbase.findDeps([po])
        for po in deps:
            requirements = deps[po]
            for req in requirements:
                provides = requirements[req]
                print req
                for prov in self.yumbase.bestPackagesFromList(provides, single_name=True):
                    self._show_requirement(req, prov)
                #print " Dep : %s -> %s " % (req, po)
        self.ended(True)


    def add_transaction(self, args):
        '''

        @param args:
        '''
        pkgstr = args[:-1]
        action = args[ -1]
        if action != 'localinstall': # Dont get a po if it is at local package
            po = self._getPackage(pkgstr)
        txmbrs = []
        if action == "install":
            txmbrs = self.yumbase.install(po)
        elif action == "update" or action == "obsolete":
            txmbrs = self.yumbase.update(po)
        elif action == "remove":
            txmbrs = self.yumbase.remove(po)
        elif action == "reinstall":
            txmbrs = self.yumbase.reinstall(po)
        elif action == "downgrade":
            txmbrs = self.yumbase.downgrade(po)
        elif action == "localinstall":
            n, e, v, r, a, rpmfile = pkgstr
            txmbrs = self.yumbase.installLocal(rpmfile)
        for txmbr in txmbrs:
            self._show_package(txmbr.po, txmbr.ts_state)
            self.debug("Added : " + str(txmbr), __name__)
        self.ended(True)

    def remove_transaction(self, args):
        '''

        @param args:
        '''
        pkgstr = args
        po = self._getPackage(pkgstr)
        self.yumbase.tsInfo.remove(po)

    def reset_transaction(self):
        '''
        reset tsInfo for a new run
        '''
        self.yumbase._tsInfo = None


    def list_transaction(self):
        '''

        '''
        for txmbr in self.yumbase.tsInfo:
            self._show_package(txmbr.po, txmbr.ts_state)
        self.ended(True)

    def _show_packages_in_transaction(self, action,result_pkgs):
        '''

        '''
        for txmbr in result_pkgs:
            self._show_package(txmbr.po, action)
        self.ended(True)

    def run_command(self, cmd, userlist):
        self.reset_transaction()
        cmd = cmd[:3]
        action = None
        result_pkgs =  []
        try:
            if cmd == 'ins':
                action = 'i'
                for pat in userlist:
                    if pat.endswith('.rpm'):
                        self.debug("This is an local rpm : %s " % pat)
                        action = 'li'
                        result_pkgs.extend(self.yumbase.installLocal(pat))
                    else:
                        result_pkgs.extend(self.yumbase.install(pattern=pat))
            elif cmd == 'rem' or cmd == 'era':
                action = 'r'
                for pat in userlist:
                    result_pkgs.extend(self.yumbase.remove(pattern=pat))
            elif cmd == 'upd':
                action = 'u'
                for pat in userlist:
                    result_pkgs.extend(self.yumbase.update(pattern=pat))
            elif cmd == 'dow':
                action = 'do'
                for pat in userlist:
                    result_pkgs.extend(self.yumbase.downgrade(pattern=pat))
            elif cmd == 'rei':
                action = 'ri'
                for pat in userlist:
                    result_pkgs.extend(self.yumbase.reinstall(pattern=pat))
        except Errors.InstallError, e:  # lint:ok
            pass
        self._show_packages_in_transaction(action,result_pkgs )
        self.reset_transaction()


    def build_transaction(self):
        '''

        '''
        rc, msgs = self.yumbase.buildTransaction()
        self.message('return_code', rc)
        for msg in msgs:
            self.message('messages', msg)
        self.message('transaction', pack(self._get_transaction_list()))
        self.message("size", self._get_download_size())
        self.ended(True)

    def _get_download_size(self):
        total = 0L
        dlpkgs = [x.po for x in self.yumbase.tsInfo.getMembers() if x.ts_state in ("i", "u")]
        for po in dlpkgs:
            total += po.size
        return format_number(total)

    def _get_transaction_list(self):
        '''
        Generate a list of the current transaction to show in at TreeView
        based on YumOutput.listTransaction.
        used yum translation wrappers, so we can reuse the allready translated strings
        '''
        out_list = []
        sublist = []
        self.yumbase.tsInfo.makelists()
        for (action, pkglist) in [(yum.i18n._('Installing'), self.yumbase.tsInfo.installed),
                            (yum_translated('Updating'), self.yumbase.tsInfo.updated),
                            (yum_translated('Removing'), self.yumbase.tsInfo.removed),
                            (yum_translated('Installing for dependencies'),
                                self.yumbase.tsInfo.depinstalled),
                            (yum_translated('Updating for dependencies'),
                                self.yumbase.tsInfo.depupdated),
                            (yum_translated('Removing for dependencies'),
                                self.yumbase.tsInfo.depremoved)]:
            for txmbr in pkglist:
                (n, a, e, v, r) = txmbr.pkgtup
                evr = txmbr.po.printVer()
                repoid = txmbr.repoid
                pkgsize = float(txmbr.po.size)
                size = format_number(pkgsize)
                alist = []
                for (obspo, relationship) in txmbr.relatedto:
                    if relationship == 'obsoletes':
                        appended = yum_translated('     replacing  %s%s%s.%s %s\n\n')
                        appended %= ("", obspo.name, "",
                                     obspo.arch, obspo.printVer())
                        alist.append(appended)
                el = (n, a, evr, repoid, size, alist)
                sublist.append(el)
            if pkglist:
                out_list.append([action, sublist])
                sublist = []
        for (action, pkglist) in [(yum_translated('Skipped (dependency problems)'),
                                   self.yumbase.skipped_packages), ]:
            for po in pkglist:
                (n, a, e, v, r) = po.pkgtup
                evr = po.printVer()
                repoid = po.repoid
                pkgsize = float(po.size)
                size = format_number(pkgsize)
                el = (n, a, evr, repoid, size, [])
                sublist.append(el)
            if pkglist:
                out_list.append([action, sublist])
                sublist = []

        return out_list


    def run_transaction(self):
        '''

        '''
        try:
            rpmDisplay = YumexRPMCallback(self)
            callback = YumexTransCallback(self)
            self.yumbase.processTransaction(callback=callback, rpmDisplay=rpmDisplay)
            self.ended(True)
        except Errors.YumBaseError, e:
            self.error(_('Error in yum Transaction : %s') % str(e))
            self.ended(False)
        except:
            self.error(_("Exception in run_transaction"))
            etype = sys.exc_info()[0]
            evalue = sys.exc_info()[1]
            self.error(str(etype) + ' : ' + str(evalue))
            self.ended(False)

    def _askForGPGKeyImport(self, po, userid, hexkeyid):
        '''
        Ask for GPGKeyImport
        This need to be overloaded in a subclass to make GPG Key import work
        '''
        self.gpg_check(po, userid, hexkeyid)
        line = sys.stdin.readline().strip('\n')
        if line == ':true':
            return True
        else:
            return False

    def _ask_for_media_change(self, prompt_first, media_id, media_name, media_num=None):
        '''
        Ask for media change
        '''
        if media_num:
            self.debug("media : %s #%d is needed" % (media_name, media_num), __name__)
        else:
            self.debug("media : %s is needed" % (media_name,), __name__)
        self.media_change(prompt_first, media_id, media_name, media_num)
        line = sys.stdin.readline().strip('\n')
        if line.startswith(':mountpoint'):
            mountpoint = unpack(line.split('\t')[1])
            self.debug("media mount point : %s" % mountpoint, __name__)
            return mountpoint
        else:
            self.debug("no media mount point returned", __name__)
            return None

    def _failureReport(self, errobj):
        """failure output for failovers from urlgrabber"""

        self.warning(_('Failure getting %s: ') % errobj.url)
        self.warning(_('Trying other mirror.'))
        raise errobj.exception

    def _interrupt_callback(self, cbobj):
        '''Handle CTRL-C's during downloads

        If a CTRL-C occurs a URLGrabError will be raised to push the download
        onto the next mirror.

        @param cbobj: urlgrabber callback obj
        '''
        # Go to next mirror
        raise URLGrabError(15, 'user interrupt')


    def get_groups(self, args):
        '''
        get category/group list
        '''
        all_groups = []
        try:
            cats = self.yumbase.comps.get_categories()
            for category in cats:
                cat = (category.categoryid, category.ui_name, category.ui_description)
                cat_grps = []
                grps = [self.yumbase.comps.return_group(g) for g in category.groups if self.yumbase.comps.has_group(g)]
                for grp in grps:
                    icon = None
                    fn = "/usr/share/pixmaps/comps/%s.png" % grp.groupid
                    if os.access(fn, os.R_OK):
                        icon = fn
                    else:
                        fn = "/usr/share/pixmaps/comps/%s.png" % category.categoryid
                        if os.access(fn, os.R_OK):
                            icon = fn

                    elem = (grp.groupid, grp.ui_name, grp.ui_description, grp.installed, icon)
                    cat_grps.append(elem)
                cat_grps.sort()
                all_groups.append((cat, cat_grps))
        except Errors.GroupsError, e:
            print str(e)
        all_groups.sort()
        self.message('groups', pack(all_groups))
        self.ended(True)

    def get_group_packages(self, args):
        '''

        @param args:
        '''
        grpid = args[0]
        grp_flt = args[1]
        grp = self.yumbase.comps.return_group(grpid)
        if grp:
            if grp_flt == 'all':
                best_pkgs = self._group_names2aipkgs(grp.packages)
            else:
                best_pkgs = self._group_names2aipkgs(grp.mandatory_packages.keys() + grp.default_packages.keys())
            for key in best_pkgs:
                # Sort the matching packages and take the last one (the best match for current arch)
                (apkg, ipkg) = sorted(best_pkgs[key], key=lambda x: x[1] or x[0])[-1]
                if ipkg:
                    self._show_package(ipkg, 'r')
                else:
                    self._show_package(apkg, 'i')
            self.ended(True)
        else:
            self.ended(False)

    # Copied from yum (output.py), an ideal candidate to be implemented in yum base.
    def _group_names2aipkgs(self, pkg_names):
        """ Convert pkg_names to installed pkgs or available pkgs, return
            value is a dict on pkg.name returning (apkg, ipkg). """
        ipkgs = self.yumbase.rpmdb.searchNames(pkg_names)
        apkgs = self.yumbase.pkgSack.searchNames(pkg_names)
        apkgs = packagesNewestByNameArch(apkgs)

        # This is somewhat similar to doPackageLists()
        pkgs = {}
        for pkg in ipkgs:
            pkgs[(pkg.name, pkg.arch)] = (None, pkg)
        for pkg in apkgs:
            key = (pkg.name, pkg.arch)
            if key not in pkgs:
                pkgs[(pkg.name, pkg.arch)] = (pkg, None)
            elif pkg.verGT(pkgs[key][1]):
                pkgs[(pkg.name, pkg.arch)] = (pkg, pkgs[key][1])

        # Convert (pkg.name, pkg.arch) to pkg.name dict
        ret = {}
        for (apkg, ipkg) in pkgs.itervalues():
            pkg = apkg or ipkg
            ret.setdefault(pkg.name, []).append((apkg, ipkg))
        return ret

    def _get_updates(self):
        if not self._updates_list:
            ygh = self.yumbase.doPackageLists(pkgnarrow='updates')
            self._updates_list = ygh.updates
        return self._updates_list

    def _get_obsoletes(self):
        if not self._obsoletes_list:
            ygh = self.yumbase.doPackageLists(pkgnarrow='obsoletes')
            self._obsoletes_list = ygh.obsoletes
        return self._obsoletes_list

    def _get_action(self, po):
        updates = self._get_updates()
        obsoletes = self._get_obsoletes()
        action = 'i'
        if self.yumbase.rpmdb.contains(po=po): # if the best po is installed, then return the installed po
            (n, a, e, v, r) = po.pkgtup
            po = self.yumbase.rpmdb.searchNevra(name=n, arch=a, ver=v, rel=r, epoch=e)[0]
            action = 'r'
        else:
            if po in updates:
                action = 'u'
            elif po in obsoletes:
                action = 'o'
            else:
                # Check if po is and older version of a installed package
                ipkgs = self.yumbase.rpmdb.searchNevra(name=po.name)
                if ipkgs:
                    ipkg = ipkgs[0]
                    if ipkg.verGT(po) and not self.yumbase.allowedMultipleInstalls(po): # inst > po
                        action = 'do'
        return po, action

    def _return_packages(self, pkgs, filter=None):
        for po in pkgs:
            po, action = self._get_action(po)
            if filter and action not in filter: # Check if action is in filter
                continue
            self._show_package(po, action)

    def _limit_package_list(self, pkgs, skip_old=True):
        good_pkgs = set()
        good_tups = {}
        for po in pkgs:
            valid = True
            if po.pkgtup in good_tups: # dont process the same po twice
                continue
            if skip_old:
                ipkgs = self.yumbase.rpmdb.searchNevra(name=po.name)
                if ipkgs:
                    ipkg = ipkgs[0]
                    if ipkg.verGT(po) and not self.yumbase.allowedMultipleInstalls(po): # inst > po
                        valid = False
            if valid:
                good_pkgs.add(po)
                good_tups[po.pkgtup] = 1
        return good_pkgs

    def search_prefix(self, prefix, show_newest_only):
        if prefix != self._last_search:
            self._last_search = prefix
            prefix += '*'
            pkgs = self.yumbase.pkgSack.returnPackages(patterns=[prefix])
            ipkgs = self.yumbase.rpmdb.returnPackages(patterns=[prefix])
            pkgs.extend(ipkgs)
            best = self._limit_package_list(pkgs)
            self._last_search_result = best
        else:
            best = self._last_search_result
        if show_newest_only:
            best = packagesNewestByName(best)
        self._return_packages(best)
        self.ended(True)

    def search(self, args):
        '''

        @param args:
        '''
        keys = unpack(args[0])
        filters = unpack(args[1])
        show_newest_only = unpack(args[2])
        if (keys,filters) != self._last_search:
            self._last_search = (keys, filters)
            pkgs = {}
            for found in self.yumbase.searchGenerator(filters, keys, showdups=True, keys=True, searchtags=False):
                pkg = found[0]
                fkeys = found[1]
                if not len(fkeys) == len(keys): # skip the result if not all keys matches
                    continue
                na = "%s.%s" % (pkg.name, pkg.arch)
                if not na in pkgs:
                    pkgs[na] = [pkg]
                else:
                    pkgs[na].append(pkg)
            packages = []
            for na in pkgs:
                best = self._limit_package_list(pkgs[na], show_newest_only)
                packages.extend(best)
            self._last_search_result = packages
        else:
            packages = self._last_search_result
        if show_newest_only:
            packages = packagesNewestByName(packages)
        self._return_packages(packages)
        self.ended(True)

    def get_repos(self, args):
        '''

        @param args:
        '''
        for repo in self.yumbase.repos.repos:
            self._show_repo(self.yumbase.repos.getRepo(repo))
        self.ended(True)


    def enable_repo(self, args):
        '''

        @param args:
        '''
        ident = args[0]
        state = (args[1] == 'True')
        self.debug("Repo : %s Enabled : %s" % (ident, state), __name__)
        repo = self.yumbase.repos.getRepo(ident)
        if repo:
            if state:
                self.yumbase.repos.enableRepo(ident)
            else:
                self.yumbase.repos.disableRepo(ident)
            self._show_repo(repo)
        else:
            self.error("Repo : %s not found" % ident)

    def enable_repo_persistent(self, args):
        '''

        @param args:
        '''
        ident = args[0]
        state = (args[1] == 'True')
        repo = self.yumbase.repos.getRepo(ident)
        if repo:
            if state:
                repo.enablePersistent()
                self.info(_("The %s repository has been enabled permanently") % ident)
            else:
                repo.disablePersistent()
                self.info(_("The %s repository has been disabled permanently") % ident)
        else:
            self.error("Repo : %s not found" % ident)
        self.ended(True)

    def set_option(self, args):
        option = args[0]
        value = unpack(args[1])
        self._applied_set_options[option] = value
        self._set_option(option, value)

        self.ended(True)

    def _set_option(self, option, value):
        if hasattr(self.yumbase.conf, option):
            setattr(self.yumbase.conf, option, value)
            self.info(_("Setting Yum Option %s = %s") % (option, value))
            for repo in self.yumbase.repos.repos.values():
                if repo.isEnabled():
                    if hasattr(repo, option):
                        setattr(repo, option, value)
                        self.debug("Setting Yum Option %s = %s (%s)" % (option, value, repo.id), __name__)

    @property
    def update_metadata(self):
        if not self._updateMetadata:
            self._updateMetadata = UpdateMetadata()
            for repo in self.yumbase.repos.listEnabled():
                try:
                    self._updateMetadata.add(repo)
                except:
                    pass # No updateinfo.xml.gz in repo
        return self._updateMetadata

    def get_update_info(self, pkg_id, obsolete):
        '''
        Get update infomation
        '''
        pkg = self._getPackage(pkg_id)
        if pkg:
            md = self.update_metadata
            notices = md.get_notices(pkg.name)
            for ret in notices:
                self.message("updateinfo", ret)
            if obsolete:
                obs = self._get_obsoleted_po(pkg)
                pkgs = [str(po) for po in obs]
                self.message("updated_po", pkgs)
            else:
                po = self._get_updated_po(pkg)
                if po:
                    self.message("updated_po", [str(po)])
                else:
                    self.message("updated_po", [])
        self.ended(True)

    def _get_updated_po(self, pkg):
        po = None
        tuples_upd = self.yumbase._getUpdates().getUpdatesTuples(name=pkg.name)
        if tuples_upd:
            tup = tuples_upd[0]
            if tup:
                new, old = tup
                po = self.yumbase.getInstalledPackageObject(old)
        return po

    def _get_obsoleted_po(self, pkg):
        obs = []
        tuples_obs = self.yumbase._getUpdates().getObsoletersTuples(name=pkg.name)
        if tuples_obs:
            for new,old in tuples_obs:
                po = self.yumbase.getInstalledPackageObject(old)
                obs.append(po)
        return obs

    def clean(self, args):
        what = args[0]
        if what == 'metadata':
            self.yumbase.cleanMetadata()
            msg = _("Cleaned metadata from local cache")
        elif what == 'dbcache':
            self.yumbase.cleanSqlite()
            msg = _("Cleaned dbcache")
        elif what == 'packages':
            self.yumbase.cleanPackages()
            msg = _("Cleaned packages from local cache")
        elif what == 'all':
            msg = _("Cleaned everything from local cache")
            self.yumbase.cleanMetadata()
            self.yumbase.cleanPackages()
            self.yumbase.cleanSqlite()
        self.info(msg)
        self.ended(True)

    def search_history(self, pattern):
        """
        Get the yum history elements
        """
        if hasattr(self.yumbase, "_history"): # Yum supports history
            tids = self.yumbase.history.search(pattern)
            yhts = self.yumbase.history.old(tids)
            for yht in yhts:
                self._show_history_item(yht)
            self.ended(True)
            self.yumbase.history.close()
        else:
            self.ended(False)

    def get_history(self, args):
        """
        Get the yum history elements
        """
        if hasattr(self.yumbase, "_history"): # Yum supports history
            yhts = self.yumbase.history.old()
            for yht in yhts:
                self._show_history_item(yht)
            self.ended(True)
            self.yumbase.history.close()
        else:
            self.ended(False)

    def get_history_packages(self, tid, data_set='trans_data'):
        tids = self.yumbase.history.old([tid])
        for yht in tids:
            if hasattr(yht,data_set): # make sure we have the data_set, yum-3.2.27 dont have trans_skip
                yhp = getattr(yht, data_set)
            else:
                yhp = []
            for pkg in yhp:
                self._show_history_package(pkg)
        self.ended(True)
        self.yumbase.history.close()

    def history_undo(self, args):
        '''
        Undo a history transaction
        '''
        tid = int(args[0])
        tids = self.yumbase.history.old([tid])
        if tids:
            self.yumbase.history_undo(tids[0])
        print "Transaction after undo"
        for txmbr in self.yumbase.tsInfo:
            print txmbr.po
        if len(self.yumbase.tsInfo) > 0:
            self.ended(True)
        else:
            self.ended(False)
        self.yumbase.history.close()

    def history_redo(self, args):
        '''
        Redo a history transaction
        '''
        tid = int(args[0])
        tids = self.yumbase.history.old([tid])
        if tids:
            self.yumbase.history_redo(tids[0])
        if len(self.yumbase.tsInfo) > 0:
            self.ended(True)
        else:
            self.ended(False)
        self.yumbase.history.close()

    def parse_command(self, cmd, args):
        ''' parse the incomming commands and do the actions '''
        try:
            self.doLock()
            if cmd == 'get-packages':       # get-packages <Package filter
                self.get_packages(args[0], args[1], args[2])
            elif cmd == 'get-attribute':
                self.get_attribute(args)
            elif cmd == 'get-changelog':
                self.get_changelog(args)
            elif cmd == 'add-transaction':
                self.add_transaction(args)
            elif cmd == 'remove-transaction':
                self.remove_transaction(args)
            elif cmd == 'reset-transaction':
                self.reset_transaction()
            elif cmd == 'list-transaction':
                self.list_transaction()
            elif cmd == 'run-transaction':
                self.run_transaction()
            elif cmd == 'build-transaction':
                self.build_transaction()
            elif cmd == 'get-groups':
                self.get_groups(args)
            elif cmd == 'get-group-packages':
                self.get_group_packages(args)
            elif cmd == 'get-packages-size':
                self.get_packages_size(args[0])
            elif cmd == 'get-packages-repo':
                self.get_packages_repo(args[0])
            elif cmd == 'get-repos':
                self.get_repos(args)
            elif cmd == 'enable-repo':
                self.enable_repo(args)
            elif cmd == 'enable-repo-persistent':
                self.enable_repo_persistent(args)
            elif cmd == 'search':
                self.search(args)
            elif cmd == 'search-prefix':
                self.search_prefix(args[0], unpack(args[1]))
            elif cmd == 'update-info':
                self.get_update_info(args[0:-1],unpack(args[-1]))
            elif cmd == 'set-option':
                self.set_option(args)
            elif cmd == 'clean':
                self.clean(args)
            elif cmd == 'get-history':
                self.get_history(args)
            elif cmd == 'get-history-packages':
                self.get_history_packages(args[0], args[1])
            elif cmd == 'history-undo':
                self.history_undo(args)
            elif cmd == 'history-redo':
                self.history_redo(args)
            elif cmd == 'search-history':
                self.search_history(unpack(args[0]))
            elif cmd == 'run-command':
                self.run_command(args[0], unpack(args[1]))
            elif cmd == 'get-available-by-name':
                self.get_available_by_name(args[0])
            elif cmd == 'get-available-downgrades':
                self.get_available_downgrades(args)
            elif cmd == 'get-dependencies':
                self.get_dependencies(args)
            elif cmd == 'die':
                self.write('die')
            else:
                self.error('Unknown command : %s' % cmd)
        finally:
            self.doUnlock()

    def dispatcher(self):
        ''' receive commands and parameter from stdin (from the client) '''
        try:
            while True:
                self.write(':ready')
                line = sys.stdin.readline().strip('\n')
                if not line or line.startswith('exit'):
                    break
                args = line.split('\t')
                ts = time.time()
                self.parse_command(args[0], args[1:])
                t = time.time() - ts
                self.debug("%s Args: %s  took %.2f s to complete" % (args[0], args[1:], t), __name__)
        except YumexBackendFatalError, e:
            self.ended(True)
            self.quit(1)
        except Errors.RepoError, e:
            # signal to the front end that we have a fatal error
            pmsg = pack(str(e))
            self.write(":fatal\t%s\t%s" % ('repo-error', pmsg))
            self.ended(True)
            self.quit(2)
        except SystemExit, e: # triggered if sys.exit(x) is called
            sys.exit(e)
        except:
            errmsg = traceback.format_exc()
            print errmsg
            self.write(":exception\t%s" % pack(errmsg))
            self.ended(True)
            self.quit(3)
        self.quit(0)

class YumexTransCallback:
    '''
    '''

    def __init__(self, base):
        '''

        @param base:
        '''
        self.base = base

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

        @param state:
        @param data:
        '''

        if state == PT_DOWNLOAD:        # Start Downloading
            self.base.yum_state('download')
        elif state == PT_DOWNLOAD_PKGS:   # Packages to download
            self.base.dnlCallback.setPackages(data, 10, 30)
        elif state == PT_GPGCHECK:
            self.base.yum_state('gpg-check')
        elif state == PT_TEST_TRANS:
            self.base.yum_state('test-transaction')
        elif state == PT_TRANSACTION:
            self.base.yum_state('transaction')


class YumexRPMCallback(RPMBaseCallback):
    '''
    '''

    def __init__(self, base):
        '''

        @param base:
        '''
        RPMBaseCallback.__init__(self)
        self.base = base
        self._last_frac = 0.0
        self._last_pkg = None
        self._printed = {}

    # Copied from yum/output.py
    def pkgname_ui(self, pkgname, ts_states=None):
        """ Get more information on a simple pkgname, if we can. We need to search
            packages that we are dealing with atm. and installed packages (if the
            transaction isn't complete). """

        if ts_states is None:
            #  Note 'd' is a placeholder for downgrade, and
            # 'r' is a placeholder for reinstall. Neither exist atm.
            ts_states = ('d', 'e', 'i', 'r', 'u')

        matches = []
        def _cond_add(po):
            if matches and matches[0].arch == po.arch and matches[0].verEQ(po):
                return
            matches.append(po)

        for txmbr in self.base.yumbase.tsInfo.matchNaevr(name=pkgname):
            if txmbr.ts_state not in ts_states:
                continue
            _cond_add(txmbr.po)

        if not matches:
            return pkgname
        fmatch = matches.pop(0)
        if not matches:
            return str(fmatch)

        show_ver = True
        show_arch = True
        for match in matches:
            if not fmatch.verEQ(match):
                show_ver = False
            if fmatch.arch != match.arch:
                show_arch = False

        if show_ver: # Multilib. *sigh*
            if fmatch.epoch == '0':
                return '%s-%s-%s' % (fmatch.name, fmatch.version, fmatch.release)
            else:
                return '%s:%s-%s-%s' % (fmatch.epoch, fmatch.name,
                                        fmatch.version, fmatch.release)

        if show_arch:
            return '%s.%s' % (fmatch.name, fmatch.arch)

        return pkgname


    def event(self, package, action, te_current, te_total, ts_current, ts_total):
        '''g
        RPM Event callback handler
        @param package:
        @param action:
        @param te_current:
        @param te_total:
        @param ts_current:
        @param ts_total:
        '''
        # Handle rpm transaction progress
        try:
            # get the package name, if a string then get info from transaction
            if type(package) not in types.StringTypes:
                pkgname = str(package)
            else:
                pkgname = self.pkgname_ui(package)

            if action in (TS_UPDATE, TS_INSTALL, TS_TRUEINSTALL): # only show progress when something is installed
                if self._last_pkg != package:
                    self._last_pkg = package
                    self._last_frac = 0.0
                frac = float(te_current) / float(te_total)
                if frac > 0.994:
                    frac = 1.0
                if frac > self._last_frac + 0.005 or frac == 1.0:
                    #self.base.debug(str([self.action[action], str(package), frac, ts_current, ts_total]))
                    self.base.yum_rpm(self.action[action], pkgname, frac, ts_current, ts_total)
                    self._last_frac = frac
                    if frac == 1.0:
                        self.show_action(pkgname, action)
            else:
                self.base.yum_rpm(self.action[action], pkgname, 1.0, ts_current, ts_total)
                self.show_action(pkgname, action)


        except:
            self.base.error('RPM Callback error : %s - %s ' % (self.action[action], str(package)))
            errmsg = traceback.format_exc()
            for eline in errmsg.split('\n'):
                if eline:
                    self.base.error(eline)

    def show_action(self, package, action):
        '''
        Show action messages after trasaction is completed
        @param package: package name
        @param action: TS Action enum
        '''
        if not str(package) in self._printed:
            self._printed[str(package)] = 1
            self.base.info(RPM_ACTIONS[action] % (package))


    def scriptout(self, package, msgs):
        '''

        @param package:
        @param msgs:
        '''
        # Handle rpm scriptlet messages
        if msgs:
            self.base.info('RPM Scriptlet: %s' % msgs)

class YumexDownloadCallback(DownloadBaseCallback):
    """ Download callback handler """
    def __init__(self, base):
        '''

        @param base:
        '''
        DownloadBaseCallback.__init__(self)
        self.base = base
        self.percent_start = 0
        self.saved_pkgs = None
        self.number_packages = 0
        self.download_package_number = 0
        self.current_name = None
        self.current_type = None
        self._current_pkg = None
        self._printed = []
        self._cur = 1
        self._tot = 1

    def setPackages(self, new_pkgs, percent_start, percent_length):
        '''

        @param new_pkgs:
        @param percent_start:
        @param percent_length:
        '''
        self._printed = []
        self.saved_pkgs = new_pkgs
        self.number_packages = float(len(self.saved_pkgs))
        self.percent_start = percent_start

    def _getPackage(self, name):
        '''

        @param name:
        '''
        if self.saved_pkgs:
            for pkg in self.saved_pkgs:
                if isinstance(pkg, YumLocalPackage):
                    rpmfn = pkg.localPkg
                else:
                    rpmfn = os.path.basename(pkg.remote_path) # get the rpm filename of the package
                if rpmfn == name:
                    return pkg
        return None

    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:
            self._cur = 1
            self._tot = 1
            if ':' in name:
                cnt, fn = name.split(':')
                name = fn.strip()
                cur, tot = cnt[1:-1].split('/')
                self._cur = cur
                self._tot = tot
            pkg = self._getPackage(name)
            self._current_pkg = pkg
            if pkg: # show package to download
                self.current_name = str(pkg)
                self.current_type = 'PKG'
            elif name == '<locally rebuilding deltarpms>': # Presto rebuilding rpm
                self.current_name = "Rebuild delta packages"
                self.current_type = 'REBUILD'
            elif name.endswith('.drpm'): # Presto delta rpm
                self.current_name = name
                self.current_type = 'PKG'
            elif not name.endswith('.rpm'):
                self.current_name = name
                self.current_type = 'REPO'
            else:
                self.current_name = name
                self.current_type = 'PKG'


        self.base.yum_dnl(self.current_type, self.current_name, val, self._cur, self._tot, fread, self.totSize, ftime)

        # keep track of how many we downloaded
        if val == 100:
            self.download_package_number += 1
            self.current_name = None
            if name not in self._printed:
                # show downloaded <filename> ( <size> )
                if name == '<locally rebuilding deltarpms>':
                    self.base.info(_('Rebuild from deltarpms completed'))
                else:
                    self.base.info(_('Downloaded : %s ( %s )') % (name, self.totSize))
                self._printed.append(name)




if __name__ == "__main__":
    pass
