#!/usr/bin/python
import sys
NAME        = "upgrade-oldest"
VERSION     = "0.2"
PURPOSE     =  "Upgrade oldest Debian packages"
USAGEPART   = "Usage: " + sys.argv[0] + " [OPTIONS]" + \
"""
 -h,   --help       - this text
 -u N, --upgrade=N  - N is one of 0, 1, 2 or 3. 2 is default.
                        0 - upgrade packages with old epoch.
                        1 - 0 and packages with old major upstream version.
                        2 - 1 and packages with old middle upstream version.
                        3 - 2 and packages with old minor upstream version.
 -i S, --include=S  - S is comma-separated list of sections to include.
                        Default is to include all sections. list:
                        # apt-cache search '.' --full |grep Section:|sort|uniq
 -a S, --action=S   - S is one of list, table, xml, apt, wajig[rs]
                        Default is to use apt-get.
                        list  - lists oldest packages
                        table - lists oldest packages as table
                        xml   - lists oldest packages as xml
                        apt   - installs using apt-get(8) install
                        wajig*- installs using wajig(1) install(r|s|rs)
 -o F, --output=F     - F is output filename. Default is stdout.\
"""
INFO = """
Author:     Alexander 'zowers' Petrov
E-mail:     zowers@front.ru
Copyright:  (c) 2005 zowers
Licence:    GPLv2
Created:    19 Jan 2005
Modified:   01 Feb 2005
RCS-ID:     $Id: upgrade-oldest $
"""
USAGE = NAME + " v." + VERSION + """
""" + PURPOSE + """
""" + USAGEPART

import re
import os
import getopt
try:
    import apt_pkg
except:
    sys.exit("Cannot find apt_pkg python module.\n"\
        "Please install python-apt Debian package.")

class DebianVersion:

    """(c) 2005 Alexander Petrov <zowers@gmail.com> - License is GPL
    Represents version of Debian Package"""
    
    
    versionReProg = re.compile("([a-zA-Z0-9]+)")

    def __init__(self, versionString):
        self.versionString = versionString
        self.epoch = 0
        self.epochPresent = 0
        self.upstreamVersion = []
        self.revision = []
        self.revisionPresent = 0
        
        upstreamStartI = 0
        upstreamEndI = len(self.versionString)
        colonI = self.versionString.find(":")
        if colonI > -1:
            self.epochPresent = 1
            upstreamStartI = colonI
            self.epoch = int(self.versionString[:colonI])
            
        lastHypenI = self.versionString.rfind("-")
        if lastHypenI > -1:
            self.revisionPresent = 1
            upstreamEndI = lastHypenI
            revision = self.versionString[lastHypenI:]
            revisionIt = self.versionReProg.finditer(revision)
            for revisionPart in revisionIt:
                self.revision.append(revision[revisionPart.start():revisionPart.end()])
        
        upstream = self.versionString[upstreamStartI:upstreamEndI]
        upstreamIt = self.versionReProg.finditer(upstream)
        for upstreamPart in upstreamIt:
            self.upstreamVersion.append(upstream[upstreamPart.start():upstreamPart.end()])
            
    def getEpoch(self):
        return self.epoch
    def getUpstreamVersion(self):
        return self.upstreamVersion
    def getUpstreamCount(self):
        return len(self.upstreamVersion)
    def getRevision(self):
        return self.revision
    def getCombined(self):
        """
        returns combined version as an array of strings:
        [epoch, upstream1,...upstreamN, revision1,...revisionM]
        """
        combined = [self.epoch]
        for v in self.getUpstreamVersion():
            combined.append(v)
        for v in self.getRevision():
            combined.append(v)
        return combined
    
    def getUpgradeLevel(self, highest):
        """Get upgrade level of current version compared to highest version"""
        currentComb = self.getCombined()
        highestComb = highest.getCombined()
        minLen = min(len(currentComb), len(highestComb))
        for i in range(minLen):
            if currentComb[i] < highestComb[i]:
                return i
        return minLen + 1

class Cache:
    "(c) 2001-2002 Nicolas Chauvat <nico@logilab.fr> - License is GPL"
    
    def __init__(self):
        apt_pkg.init()
        self._cache = apt_pkg.GetCache()
        

    def installedPackages(self):
        return [package for package in self._cache.Packages
                if self.isInstalled(package.Name)]

    def unInstalledPackages(self):
        return [package for package in self._cache.Packages
                if not self.isInstalled(package.Name)]

    def isInstalled(self, name):
        return (self._cache[name].CurrentVer is not None)

    def highestVersion(self, name):
        package = self._cache[name]
        versions = package.VersionList
        version = versions[0]
        for other_version in versions:
            version = max_version(version, other_version)
        return version

def max_version(version, other_version):
    if apt_pkg.VersionCompare(version.VerStr, other_version.VerStr)<0:
        return other_version
    else :
        return version
    
def usage():
    print USAGE
    sys.exit(0)

def main():
    try:
        optlist, args = getopt.getopt(sys.argv[1:], 
            "hu:i:a:o:", 
            ["help", "upgrade=", "include=", "action=", "output="])
    except getopt.GetoptError:
        usage()
        
    upgradeLevel = 2
    includeSections = []
    action = "apt"
    
    for opt, optval in optlist:
        if opt in ("-h", "--help"):
            action = "help"
            continue
        if opt in ("-u", "--upgrade"):
            try:
                upgradeLevel = int(optval)
                if upgradeLevel > 3:
                    raise OverflowError("upgradeLevel")
            except:
                print "Upgrade Level must be one of 0, 1, 2 or 3"
                usage()
            continue
        if opt in ("-i", "--include"):
            includeSections = optval.split(",")
            continue
        if opt in ("-a", "--action"):
            action = optval
            continue
        if opt in ("-o", "--output"):
            sys.stdout = open(optval, "w")
            continue
        print "Bad option: " + opt + " " + optval
        usage()
    
    if action == "help":
        usage()
    
    cache = Cache()
    
    upgradable = []
    for package in cache.installedPackages() :
        current = package.CurrentVer
        highest = cache.highestVersion(package.Name)
        if current is not max_version(current, highest):
            currentDv = DebianVersion(current.VerStr)
            highestDv = DebianVersion(highest.VerStr)
            level = currentDv.getUpgradeLevel(highestDv)
            if level <= upgradeLevel:
                if len(includeSections) == 0 \
                or package.Section in includeSections :
                    upgradable.append( (package.Name, current, highest, level) )
    
    if len(upgradable) == 0:
        print "Nothing to upgrade"
        sys.exit(0)
    
    packages = ""
    format = "%-30s %-20s %-20s %s"
    doc = None
    docEl = None
    if action == "table":
        print format % ("Package", "CurrentVersion", "AvailableVersion", "Level")
    elif action == "xml":
        from xml.dom.minidom import getDOMImplementation
        impl = getDOMImplementation()
        doc = impl.createDocument(None, "packages", None)
        docEl = doc.documentElement
    # cycle through packages
    for package in upgradable:
        packages = packages + package[0] + " "
        if action == "table":
            print format % (package[0], package[1].VerStr, package[2].VerStr, package[3])
        elif action == "xml":
            packageEl = doc.createElement("package")
            docEl.appendChild(packageEl)
            packageEl.setAttribute("name", package[0])
            el = doc.createElement("currentVer")
            packageEl.appendChild(el)
            textEl = doc.createTextNode("")
            el.appendChild(textEl)
            textEl.nodeValue = package[1].VerStr
            el = doc.createElement("highestVer")
            packageEl.appendChild(el)
            textEl = doc.createTextNode("")
            el.appendChild(textEl)
            textEl.nodeValue = package[2].VerStr
            el = doc.createElement("level")
            packageEl.appendChild(el)
            textEl = doc.createTextNode("")
            el.appendChild(textEl)
            textEl.nodeValue = package[3]
            
    
    if action == "list":
        print "Packages:\n" + packages
    elif action == "table":
        pass
    elif action == "xml":
        doc.writexml(sys.stdout)
    elif action == "apt":
        print "Installing using apt-get"
        os.system("apt-get install " + packages)
    elif action.startswith("wajig"):
        actSfx = action[len("wajig"):]
        print "Installing using wagig install%s" % actSfx
        os.system("wajig install%s %s" % (actSfx, packages))
    else:
        print "Unknown action, listing packages"
        print "Packages:\n" + packages

if __name__ == "__main__":
    main()
