#!/usr/bin/python -tt
#
# Copyright 2005-2007  Red Hat, Inc.
# Copyright 2007       Debarshi Ray
#
# Jeremy Katz <katzj@redhat.com>
# Debarshi Ray <rishi@fedoraproject.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; version 2 only
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

import os, sys
import gettext
import logging
import shutil
import tarfile

try:
    import gtk
    import gtk.glade
    import gtk.gdk as gdk
    import gobject
except Exception, e:
    print >> sys.stderr, "Unable to import modules.  Maybe you're not running under X?"
    sys.exit(1)

from rhpl.exception import installExceptionHandler

from rpmUtils.miscutils import compareEVR
import rpmUtils.arch
import yum.misc
from yum.packages import parsePackages, YumLocalPackage
from yum import logginglevels
import rpm

from pirut import *
from pirut.constants import *
from pirut.Errors import *

if os.path.exists("data/system-install-yumpacks.glade"):
    gladefn = "data/system-install-yumpacks.glade"
else:
    gladefn = "/usr/share/opyum/ui/system-install-yumpacks.glade"

t = gettext.translation(I18N_DOMAIN, "/usr/share/locale", fallback = True)
_ = t.lgettext

class YumPackInstaller(GraphicalYumBase):
    def __init__(self, opyum_dir):
        """
        Initialize the YumPack installer.
        """
        
        self.opyumDir = opyum_dir
        if not os.access(self.opyumDir, os.F_OK):
            os.makedirs(self.opyumDir)

        self.tmpDir = self.opyumDir + "/.tmp"

        self.xml = gtk.glade.XML(gladefn, domain=I18N_DOMAIN)
        self.mainwin = self.xml.get_widget("InstallerWindow")
        # self.mainwin.set_icon_from_file("/usr/share/pirut/pixmaps/installpkg.png")
        self._connectSignals()
        self._createPackageStore()
        self.mainwin.connect("delete_event", self.quit)

        # note that nothing which takes "time" should be called here!
        GraphicalYumBase.__init__(self, False)
        self.unsignedok = True
        self.repos_setup = False
        self._enabledRepos = self.repos.listEnabled()
        map(lambda x: x.disable(), self._enabledRepos)

    def _connectSignals(self):
        sigs = { "on_InstallerWindow_destroy": self.quit,
                 "on_quitButton_clicked": self.quit,
                 "on_applyButton_clicked": self._apply }
        self.xml.signal_autoconnect(sigs)

    def _createPackageStore(self):
        self.store = gtk.ListStore(gobject.TYPE_PYOBJECT)
        
        tree = self.xml.get_widget("packageList")
        tree.set_model(self.store)

        column = gtk.TreeViewColumn(None, None)
        column.set_clickable(True)
        txtr = gtk.CellRendererText()
        column.pack_start(txtr, True)
        column.set_cell_data_func(txtr, self.get_pkg_info)
        tree.append_column(column)

    def get_pkg_info(self, col, cell, model, i):
        pkg = model.get_value(i, 0)
        nevra = "%s" %(pkg,)
        desc = pkg.returnSimple('summary')
        text = "<span weight=\"bold\">%s</span>\n%s" %(nevra, desc)
        text.strip()
        cell.set_property("markup", text)

    def doRefresh(self, *args):
        self._busyCursor()
        self.populatePackages()
        self._normalCursor()

    def logDialog(self, msg):
        print msg
        d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO,
                              gtk.BUTTONS_OK, msg)
        d.show_all()
        d.run()
        d.destroy()           

    def errorDialog(self, msg):
        print msg
        d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL, gtk.MESSAGE_ERROR,
                              gtk.BUTTONS_OK, msg)
        d.show_all()
        d.run()
        d.destroy()           

    def _populateLocalPackages(self, pkgs):
        self.store.clear()

        updates = []
        installs = []
        for pkg in pkgs:
            try:
                po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg)
            except yum.Errors.MiscError, e:
                log = logging.getLogger("yum")
                log.warn("Cannot open file: %s. Skipping." %(pkg,))
                continue

            if po.hdr[rpm.RPMTAG_SOURCEPACKAGE] == 1: # ugh.
                self.logDialog(_("Cannot install source packages."))
                continue

            inst = self.rpmdb.searchNevra(name=po.name)
            for instpo in inst:
                (n,a,e,v,r) = po.pkgtup
                (inn,ia,ie,iv,ir) = instpo.pkgtup
                rc = compareEVR((ie,iv,ir), (e,v,r))
                if rc < 0: # update
                    if n not in self.conf.exactarchlist or a == ia:
                        updates.append((po, instpo))
                    else:
                        self.logDialog(_("Can't install a different arch of %s than already installed.") %(pkg,))
                        continue
                elif rc == 0: # same, ignore
                    self.logDialog("%s is already installed" %(pkg,))
                elif rc > 0:
                    self.logDialog("A newer version than %s is already installed" %(pkg,))
            if len(inst) == 0:
                installs.append(po)

        for po in installs:
            self.localPackages.append(po)
            self.tsInfo.addInstall(po = po)
            self.store.append([po])
        for (po, oldpo) in updates:
            self.localPackages.append(po)
            self.tsInfo.addUpdate(po, oldpo)
            self.store.append([po])

    def _populateRepoPackages(self, pkgs):
        self.store.clear()
        self._ensure_repos()
        avail = self.pkgSack.returnPackages()
        
        toBeInstalled = {} # keyed on name
        updates = []

        # FIXME:
        # Parsing command line options that aren't local packages gets complex,
        # partly because the user has the option of specifying or not specifying
        # arch and version. The code below is lifted verbatim from the cli.py
        # implementation of 'yum install'. It perhaps should be moved into the
        # yum core in some fashion.
        # 
        ########################################
        # BEGIN CUT-AND-PASTE from cli.py
        #
        
        self.verbose_logger.log(logginglevels.INFO_2,
            _('Parsing package install arguments'))
        for arg in pkgs:
            arglist = [arg]
            exactmatch, matched, unmatched = parsePackages(avail, arglist, 
                                                               casematch=1)
            if len(unmatched) > 0: # if we get back anything in unmatched, check it for a virtual-provide
                arg = unmatched[0] #only one in there
                self.verbose_logger.debug('Checking for virtual provide or file-provide for %s', 
                    arg)
                try:
                    mypkg = self.returnPackageByDep(arg)
                except yum.Errors.YumBaseError, e:
                    self.errorDialog(_('No Match for argument: %s') % arg)
                    # Continuing on and installing only some packages from the requested
                    # set is probably not useful
                    self.quit()
                else:
                    arg = '%s:%s-%s-%s.%s' % (mypkg.epoch, mypkg.name,
                                              mypkg.version, mypkg.release,
                                              mypkg.arch)
                    emtch, mtch, unmtch = parsePackages(avail, [arg])
                    exactmatch.extend(emtch)
                    matched.extend(mtch)
            
            installable = yum.misc.unique(exactmatch + matched)
            exactarchlist = self.conf.exactarchlist
            
            # we look through each returned possibility and rule out the
            # ones that we obviously can't use
            for pkg in installable:
                if self.rpmdb.installed(po=pkg):
                    self.verbose_logger.log(logginglevels.DEBUG_3,
                        'Package %s is already installed, skipping', pkg)
                    continue
                
                # everything installed that matches the name
                installedByKey = self.rpmdb.searchNevra(name=pkg.name)
                comparable = []
                for instpo in installedByKey:
                    if rpmUtils.arch.isMultiLibArch(instpo.arch) == rpmUtils.arch.isMultiLibArch(pkg.arch):
                        comparable.append(instpo)
                    else:
                        self.verbose_logger.log(logginglevels.DEBUG_3,
                            'Discarding non-comparable pkg %s.%s', instpo.name, instpo.arch)
                        continue
                        
                # go through each package 
                if len(comparable) > 0:
                    for instpo in comparable:
                        if pkg.EVR > instpo.EVR: # we're newer - this is an update, pass to them
                            if instpo.name in exactarchlist:
                                if pkg.arch == instpo.arch:
                                    updates.append((pkg, instpo))
                            else:
                                updates.append((pkg, instpo))
                        elif pkg.EVR == instpo.EVR: # same, ignore
                            continue
                        elif pkg.EVR < instpo.EVR: # lesser, check if the pkgtup is an exactmatch
                                           # if so then add it to be installed
                                           # if it can be multiply installed
                                           # this is where we could handle setting 
                                           # it to be an 'oldpackage' revert.
                                           
                            if pkg in exactmatch and self.allowedMultipleInstalls(pkg):
                                if not toBeInstalled.has_key(pkg.name): toBeInstalled[pkg.name] = []
                                toBeInstalled[pkg.name].append(pkg)
                else: # we've not got any installed that match n or n+a
                    self.verbose_logger.log(logginglevels.DEBUG_1, 'No other %s installed, adding to list for potential install', pkg.name)
                    if not toBeInstalled.has_key(pkg.name): toBeInstalled[pkg.name] = []
                    toBeInstalled[pkg.name].append(pkg)

        # this is where I could catch the installs of compat and multilib 
        # arches on a single yum install command. 
        pkglist = []
        for name in toBeInstalled.keys():
            pkglist.extend(self.bestPackagesFromList(toBeInstalled[name]))
        #
        # END CUT-AND-PASTE from cli.py
        #########################################
        
        for po in pkglist:
            self.tsInfo.addInstall(po = po)
            self.store.append([po])
        for (po, oldpo) in updates:
            self.tsInfo.addUpdate(po, oldpo)
            self.store.append([po])

    def extractYumPacks(self):
        """
        Extract the packages from the given YumPacks.
        """
        
        if os.access(self.tmpDir, os.F_OK):
            shutil.rmtree(self.tmpDir)
        os.makedirs(self.tmpDir)

        for yumpack in sys.argv[1:]:
            p = tarfile.TarFile(name=yumpack, mode="r")
            members = p.getnames()
            num = len(members)
            for i in xrange(num):
                p.extract(members[i], path=self.tmpDir)
            p.close()
        
        packages = os.listdir(self.tmpDir + "/yumpack")

        # FIXME: Use functional programming techniques.
        num = len(packages)
        for i in xrange(num):
            packages[i] = self.tmpDir + "/yumpack/" + packages[i]
        
        return packages

    def populatePackages(self):
        locals = []
        others = []
        
        packages = self.extractYumPacks()
        for pkg in packages:
            if os.path.exists(pkg):
                locals.append(pkg)
            else:
                others.append(pkg)

        if len(locals) > 0:
            self._populateLocalPackages(locals)
        if len(others) > 0:
            self._populateRepoPackages(others)

        if not hasattr(self, "tsInfo") or len(self.tsInfo) <= 0:
            self.errorDialog(_("No packages were given for installation."))
            self.quit()
    
    def quit(self, *args, **kwargs):
        """
        Exit handler.
        """
        
        shutil.rmtree(self.tmpDir)
        GraphicalYumBase.quit(self)

    def _busyCursor(self):
        self.mainwin.window.set_cursor(gdk.Cursor(gdk.WATCH))
        self.mainwin.set_sensitive(False)

    def _normalCursor(self):
        self.mainwin.window.set_cursor(None)
        self.mainwin.set_sensitive(True)

    def _ensure_repos(self):
        if self.repos_setup:
            return
        
        map(lambda x: x.enable(), self._enabledRepos)
        self.doRefreshRepos()
            
        self.repos_setup = True

    def _apply(self, *args):
        # do a depsolve to see if we need to grab any other stuff... this is
        # kind of a hack knowing what resolveDeps does
        self.populateTs(test=1)
        deps = self.ts.check()
        if deps:
            self._ensure_repos()
        try:
            output = self.applyChanges(self.mainwin)
        except PirutError:
            self.quit()
        except OSError:
            d = gtk.MessageDialog(self.mainwin, gtk.DIALOG_MODAL,
                                  gtk.BUTTONS_OK,
                                  _("Unable to install software.  Error "
                                    "opening files."))
            d.show_all()
            d.run()
            d.destroy()
            self.quit()

        d = PirutDetailsDialog(self.mainwin, gtk.MESSAGE_INFO, gtk.BUTTONS_OK,
                               _("Software installed successfully."))
        if output:
            d.format_secondary_text("Some warnings were given.")
            d.set_details(buffer = outputDictAsTextBuffer(output))
        d.run()
        d.destroy()
        self.quit()
            

    def run(self):
        self.mainwin.show_all()
        while gtk.events_pending(): gtk.main_iteration()
        self.doRefresh()
        gtk.main()

def main():
    gtk.glade.bindtextdomain(I18N_DOMAIN, "/usr/share/locale")
    try:
        # right now, we have to run privileged...
        if os.getuid() != 0:
            raise PirutError(_("Must be run as root."))
        pkginst = YumPackInstaller(os.getenv("HOME")+"/.opyum")
    except PirutError, e:
        startupError(e)
    pkginst.run()

if __name__ == "__main__":
    installExceptionHandler("pirut", "")
    main()
