#!/usr/bin/python -u
"""Webconfig YUM Helper

This is a YUM helper program which installs/removes Webconfig App RPMs

    --help, -h  Display this message.
    --install, -i   Install specified RPMs.
    --uninstall, -u Uninstall specified RPMs.

Examples:
    Install modules foo and bar:
    # wc-yum -i app-foo app-bar

    Uninstall modules foo and bar:
    # wc-yum --uninstall app-foo app-bar
"""
import os
import sys
import yum
import rpm
import logging
import getopt
import json
import time
from yum import Errors
from yum.callbacks import DownloadBaseCallback
from yum.callbacks import ProcessTransBaseCallback
from yum.rpmtrans import RPMBaseCallback
from yum.yumRepo import YumRepository as Repository
from xml.etree.ElementTree import parse

# Global status object
status = { 'code': 0, 'progress': 0, 'overall': 0, 'details': None, 'errmsg': None }

def wcLogger(msg, *k, **kw):
    pass

class wcTransactionCallback(ProcessTransBaseCallback):
    def __init__(self):
        ProcessTransBaseCallback.__init__(self)
        
    def event(self, state, data = None):
        if state in PT_MESSAGES.keys():
            status['details'] = PT_MESSAGES[state]
            print json.dumps(status)

class wcDownloadCallback(DownloadBaseCallback):
    def __init__(self, pkg_count):
        DownloadBaseCallback.__init__(self)
        self.pkg_index = 0
        self.pkg_count = pkg_count * 2

    def updateProgress(self, name, frac, fread, ftime):
        pct = int(frac * 100)
        if pct == 100:
            self.pkg_index = self.pkg_index + 1
        package = name.replace('.rpm', '')
        status['progress'] = pct
        status['details'] = "Downloading: %s" %(package)
        status['overall'] = int(self.pkg_index * 100 / self.pkg_count)
        print json.dumps(status)

class wcProgressCallback(RPMBaseCallback):
    def __init__(self, pkg_count):
        RPMBaseCallback.__init__(self)
        self.lastmsg = None
        self.lastpackage = None
        self.pkg_index = pkg_count
        self.pkg_count = pkg_count * 2

    def event(self, package, action, te_current, te_total, ts_current, ts_total):
        package = str.replace(str(package), ' ', '')
        if te_current == te_total:
            self.pkg_index = self.pkg_index + 1
        status['progress'] = int(te_current * 100 / te_total)
        status['overall'] = int(self.pkg_index * 100 / self.pkg_count)
        status['details'] = "%s: (%d/%d): %s" %(self.action[action], ts_current, ts_total, package)
        msg = "%s: %s" %(status['details'], status['progress'])
        if msg != self.lastmsg:
            print json.dumps(status)

        self.lastmsg = msg;
        self.lastpackage = package

    def scriptout(self, package, msgs):
        if msgs:
            print msgs,

class wcYumHelper(yum.YumBase):
    def __init__(self, pkgs):
        yum.YumBase.__init__(self)

        status['details'] = 'Fetching repositories.'
        print json.dumps(status)

        installpkgs = []
        updatepkgs = []
        donothingpkgs = []

        self.conf.rpm_check_debug = False

        status['progress'] = 35
        status['details'] = 'Updating repositories and preparing package dependencies... this may take a few minutes.'
        print json.dumps(status)

        searchlist = ['name']
        matching = self.searchGenerator(searchlist, pkgs)

        for (po, matched_value) in matching:
            status['progress'] = 100
            status['overall'] = 1
            print json.dumps(status)

            if po.name not in pkgs:
                continue

            installedByKey = self.rpmdb.searchNevra(name=po.name)

            if len(installedByKey) == 0:
                if len(installpkgs) == 0:
                    installpkgs.append(po)
                    continue
                found = False
                for newpo in installpkgs:
                    if newpo.name == po.name:
                        found = True
                        if po.EVR > newpo.EVR:
                            installpkgs.remove(newpo)
                            installpkgs.append(po)
                if found == False:
                    installpkgs.append(po)
                continue

            for installed_pkg in installedByKey:
                if installed_pkg.name != po.name:
                    donothingpkgs.append(po)
                    continue
                if po.EVR > installed_pkg.EVR:
                    found = False
                    for (new, old) in updatepkgs:
                        if new.name != po.name:
                            continue
                        if po.EVR > new.EVR:
                            updatepkgs.remove((new, old))
                        else:
                            found = True
                    if found == False:
                        updatepkgs.append((po, installed_pkg))
                elif po.EVR == installed_pkg.EVR:
                    donothingpkgs.append(po)
                else:
                    donothingpkgs.append(po)

        for po in installpkgs:
            self.install(po)

        for (po, oldpo) in updatepkgs:
            self.tsInfo.addUpdate(po, oldpo)

        self.repos.setProgressBar(wcDownloadCallback(100))
        self.buildTransaction()
        if len(self.tsInfo) == 0:
            status['code'] = 1
            raise Exception("no packages selected for install.")

        self.processTransaction(None, None, wcProgressCallback(len(self.tsInfo)))
        status['overall'] = 100
        status['details'] = 'Installation complete.'
        print json.dumps(status)

class wcRpmHelper:
    def __init__(self):
        self.pkg_count = 0;
        self.pkg_index = 0;

    def transactionCallback(self, reason, amount, total, key, client_data):
        if reason == rpm.RPMCALLBACK_UNINST_START:
            status['progress'] = 0 
            status['overall'] = int(self.pkg_index * 100 / self.pkg_count)
            status['details'] = 'Uninstalling: (%d/%d): %s' %(self.pkg_index + 1, self.pkg_count, key)
            print json.dumps(status)
        if reason == rpm.RPMCALLBACK_UNINST_PROGRESS:
            status['progress'] = int(total * 100 / amount)
            status['details'] = 'Uninstalling: (%d/%d): %s' %(self.pkg_index + 1, self.pkg_count, key)
            print json.dumps(status)
        if reason == rpm.RPMCALLBACK_UNINST_STOP:
            status['progress'] = 100
            status['details'] = 'Uninstalled: (%d/%d): %s' %(self.pkg_index + 1, self.pkg_count, key)
            print json.dumps(status)

    def isPackageInstalled(self, pkg):
        ts = rpm.TransactionSet()
        mi = ts.dbMatch('name', pkg)
        try:
            for h in mi:
                return True
            return False
        except StopIteration:
            return False

    def removePackages(self, pkgs):
        self.pkg_index = 0
        self.pkg_count = len(pkgs)
        ts = rpm.TransactionSet()
        for pkg in pkgs:
            ts.addErase(pkg)
        unmet = ts.check()
        if len(unmet) > 0:
            status['code'] = 2
            status['details'] = unmet;
            raise Exception("unmet dependencies.")
        ts.order()
        ts.run(self.transactionCallback, 1)
        status['overall'] = 100
        status['details'] = 'Uninstall complete.'
        print json.dumps(status)

def main():
    mode = 0
    try:
        opts, args = getopt.getopt(sys.argv[1:], "hi:u:", ["help", "install", "uninstall"])
    except getopt.error, msg:
        print "Error, %s." % msg
        print "Try --help for usage."
        sys.exit(-1)
    pkgs = []
    for o, a in opts:
        if o in ("-h", "--help"):
            print __doc__
            sys.exit(1)
        if o in ("-i", "--install"):
            mode = 1
            pkgs.append(a)
        if o in ("-u", "--uninstall"):
            mode = 2
            pkgs.append(a)

    verbose_logger = logging.getLogger("yum.verbose.YumPlugins")
    setattr(verbose_logger, 'log', wcLogger)
    setattr(verbose_logger, 'info', wcLogger)
    setattr(verbose_logger, 'debug', wcLogger)
    setattr(verbose_logger, 'error', wcLogger)
    verbose_logger = logging.getLogger("yum.Errors")
    setattr(verbose_logger, 'log', wcLogger)
    setattr(verbose_logger, 'info', wcLogger)
    setattr(verbose_logger, 'debug', wcLogger)
    setattr(verbose_logger, 'error', wcLogger)

    if mode == 0:
        print "Error, no mode specified."
        print "Try --help for usage."
        sys.exit(-1)
    for a in args:
        pkgs.append(a)
    wc_rpm = wcRpmHelper()
    try:
        if mode == 1:
            wc_yum = wcYumHelper(pkgs)
        elif mode == 2:
            wc_rpm.removePackages(pkgs)
    except Errors.YumDownloadError, errstr:
        status['code'] = -1
        status['errmsg'] = "Exception: Errors were encountered while downloading"
        print json.dumps(status)
        sys.exit(-1)
    except Exception, msg:
        if status['code'] == 0:
            status['code'] = -1
            status['errmsg'] = "Exception: %s" %msg
        else:
            status['errmsg'] = "Error, %s" %msg
        print json.dumps(status)
        sys.exit(-1)
    sys.exit(0)

if __name__ == "__main__":
    main()

# vi: expandtab shiftwidth=4 softtabstop=4 tabstop=4 syntax=python
