#!/usr/bin/python

#
# Currently implemented tests:
#   - find out if PPAs exist
#   - find out if package exists in PPA
#   - find out if package is available for current releases
#   - find out if package built
#   - find out which installed files are not in /opt
#   - find out if there are binaries in the source
#

from launchpadlib.launchpad import Launchpad

import subprocess
import urllib
import sys
import os
import re

USAGE = """Arguments not specified correctly.
Usage: arb-lint <ppa> <package>
ie:    arb-lint ~dholbach/+archive/ppa xwax"""

class BinaryPackages(object):
    uncompress_directory = "test-deb"
    downloaded_files = []
    uncompressed_files = []

    def _uncompress_debs(self):
        for deb in self.downloaded_files:
            subprocess.call(["dpkg", "-x", deb, self.uncompress_directory])
        for path, dirs, filenames in os.walk(self.uncompress_directory):
            for filename in filenames:
                real_filename = re.sub(r"^%s" % self.uncompress_directory, 
                                       "", 
                                       os.path.join(path, filename))
                self.uncompressed_files.append(real_filename)

    def find_files_not_in_opt(self):
        self._uncompress_debs()
        opt_files = filter(lambda a: not a.startswith("/opt"), self.uncompressed_files)
        if opt_files:
            print "WARNING: The following files are not installed in /opt:\n\t",
            print "\n\t".join(self.uncompressed_files)

    def __del__(self):
        subprocess.call(["rm", "-r", self.uncompress_directory])

class SourcePackage(object):
    downloaded_files = []
    uncompress_directory = os.path.abspath("test-source")
    uncompressed_files = []

    def _uncompress_source_package(self):
        dsc_file = filter(lambda a: a.endswith(".dsc"), self.downloaded_files)[0]
        subprocess.call(["dpkg-source", "-x", dsc_file, self.uncompress_directory])
        for path, dirs, filenames in os.walk(self.uncompress_directory):
            for filename in filenames:
                real_filename = os.path.join(path, filename)
                self.uncompressed_files.append(real_filename)

    def find_binaries(self):
        self._uncompress_source_package()
        analysis = []
        for source_file in self.uncompressed_files:
            output = subprocess.Popen(["file", "-b", source_file], 
                                      stdout=subprocess.PIPE).communicate()[0]
            analysis += [ source_file, output ]
        binary_files = filter(lambda a: a[1].startswith("ELF"), analysis)
        if binary_files:
            print "ERROR: The following files in the source code are compiled code:\n\t",
            print "\n\t".join(map(lambda a: a[0], binary_files))


    def __del__(self):
        subprocess.call(["rm", "-r", self.uncompress_directory])


class PPA(object):
    person_name = ""
    ppa_name = ""
    lp = None
    ppa = None
    source = None

    def __init__(self, ppa_url):
        for regex in [ "^https://launchpad.net/", "lp:" ]:
            ppa_url = re.sub(regex, "", ppa_url)
        found = re.findall(r"^~(.+?)/\+archive\/(.+?)$", ppa_url)
        if not found:
            print >> sys.stderr, USAGE
            sys.exit(1)
        self.person_name = found[0][0]
        self.ppa_name = found[0][1]
        self.lp = Launchpad.login_with("arb-lint", "production", 
                                       os.path.expanduser("~/.launchpadlib/"))
        person = self.lp.people[self.person_name]
        found = filter(lambda a: a.name == self.ppa_name, person.ppas)
        if not found:
            print >> sys.stderr, "PPA '%s' not found." % self.ppa_name
            sys.exit(1)
        self.ppa = found[0]

    def _find_newest_release(self, sources):
        active_ubuntu_releases = filter(lambda a: a.active, 
                                    self.lp.distributions["ubuntu"].series)
        for release in active_ubuntu_releases:
            for source in sources:
                if release.name == source.distro_series.name:
                    return source
        return None

    def download_package(self, package_name):
        source_packages = self.ppa.getPublishedSources(source_name=package_name, status="Published")
        if not len(source_packages):
            print >> sys.stderr, "BinaryPackages '%s' not found in PPA '%s'." % \
                    (package_name, self.ppa_name)
            sys.exit(1)
        self.source = self._find_newest_release(source_packages)
        if not self.source:
            print >> sys.stderr, "BinaryPackages '%s' not available for any current distro releases." % \
                    (package_name, self.ppa_name)
            sys.exit(1)
        if not len(self.source.getPublishedBinaries()):
            print "ERROR: BinaryPackages '%s' did not build." % self.ppa_name
            sys.exit(1)
        cwd = os.getcwd()
        urls = self.source.sourceFileUrls()
        urls.extend(self.source.binaryFileUrls())
        binary_packages = BinaryPackages()
        source_package = SourcePackage()
        for url in urls:
            file_name = os.path.join(cwd, urllib.unquote_plus(os.path.basename(url)))
            urllib.urlretrieve(url, file_name)
            if file_name.endswith(".deb"):
                binary_packages.downloaded_files.append(file_name)
            else:
                source_package.downloaded_files.append(file_name)
        return (binary_packages, source_package)


def main():
    if len(sys.argv) != 3:
        print >> sys.stderr, USAGE
        sys.exit(1)

    ppa_url = sys.argv[1]
    package_name = sys.argv[2]

    ppa = PPA(ppa_url)
    (binary_packages, source_package) = ppa.download_package(package_name)
    binary_packages.find_files_not_in_opt()
    source_package.find_binaries()
    

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print >> sys.stderr, "Aborted."
        sys.exit(1)
