#! /usr/bin/python

import HTMLParser
import fnmatch
import getopt
import getpass
import os
import posix
import re
import shutil
import sys
import tempfile
import time
import urllib
import xml.etree.ElementTree

verbose = False

class BambooClient:
    def __init__(self):
        self.opener = urllib.URLopener()
        self.auth_cookie = None
        self.bambooconf = {}
        self.read_config()

    def read_config(self, path = None):
        self.bambooconf = {}

        if path == None:
            path = os.path.join(os.path.expanduser("~"), ".bamboo")
        conf_file = None

        try:
            conf_file = file(path, "r")
            for line in conf_file:
                (name, value) = map(lambda l: l.strip(), line.split("="))
                self.bambooconf[name] = value
            conf_file.close()
            conf_file = None
        except:
            self.bambooconf['resturi'] = 'http://bamboo.globus.org/api/rest/'
            self.bambooconf['bamboouri'] = 'http://bamboo.globus.org/browse/'
            prompt = "Username [%s]: " % (getpass.getuser())
            username = None
            try:
                username = input(prompt)
            except:
                username = getpass.getuser()
            self.bambooconf['username'] = username

            bamboopass = None
            while bamboopass is None:
                bamboopass = getpass.getpass("Enter bamboo password: ")
            self.bambooconf['password'] = bamboopass

        if conf_file is not None:
            conf_file.close()
            conf_file = None

    def login(self):
        now = time.time()
        mtime = 0
        bamboo_login_path = os.path.join(os.path.expanduser("~"), ".bambooauth")

        try:
            mtime = os.path.getmtime(bamboo_login_path)
        except:
            pass

        if (now - mtime) < (30 * 60):
            auth_file = file(bamboo_login_path, "r")
            self.auth_cookie = auth_file.readline().strip()
        else:
            authuri = self.bambooconf['resturi'] + "login.action"
            authuri += "?username=%s" %(self.bambooconf['username'])
            authuri += "&password=%s" %(self.bambooconf['password'])

            auth_conn = self.opener.open(authuri)

            parser = xml.etree.ElementTree.parse(auth_conn)
            self.auth_cookie = parser.find("auth").text
            auth_conn.close()
            oldmask = posix.umask(077)
            auth_file = file(bamboo_login_path, "w")
            auth_file.write(self.auth_cookie + "\n")
            auth_file.close()
            posix.umask(oldmask)

    def logout(self):
        if self.auth_cookie is not None:
            logout_conn = self.invoke("logout.action")
            self.auth_cookie = None
            logout_conn.close()

        bamboo_login_path = os.path.join(os.path.expanduser("~"), ".bambooauth")
        if os.path.exists(bamboo_login_path):
            posix.remove(bamboo_login_path)

    def invoke(self, action, args = {}):
        if self.auth_cookie is None:
            self.login()
        else:
            now = time.time()
            bamboo_login_path = os.path.join(os.path.expanduser("~"), ".bambooauth")
            os.utime(bamboo_login_path, None)


        actionuri = self.bambooconf['resturi'] + action
        actionuri += "?auth=" + self.auth_cookie
        if args is not None:
            for i in args.keys():
                actionuri += "&%s=%s" %(i, args[i])
        if verbose:
            print actionuri
        return self.opener.open(actionuri)

    def update_and_build(self, buildKey):
        conn = self.invoke("updateAndBuild.action", { 'buildKey': buildKey })
        parser = xml.etree.ElementTree.parse(auth_conn)
        response_string = parser.find("string").text
        conn.close()
        return response_string
    
    def list_projects(self):
        projects_conn = self.invoke("listProjectNames.action")

        name = None
        response = {}
        for event, elem in xml.etree.ElementTree.iterparse(projects_conn):
            if elem.tag == 'name':
                name = elem.text
            elif elem.tag == 'key':
                response[elem.text] = name
        return response

    def get_project_details(self, projectKey):
        details_conn = self.invoke(
                "getProjectDetails.action", 
                { 'projectKey': projectKey })
        response = {}
        project = None
        name = None
        key = None
        currentStatus = None

        for event, elem in xml.etree.ElementTree.iterparse(details_conn):
            if elem.tag == 'project':
                response[key] = { 'name': name, 'currentStatus': currentStatus }
                project = None
                name = None
                key = None
                currentStatus = None
            elif elem.tag == 'key':
                key = elem.text
            elif elem.tag == 'name':
                name = elem.text
            elif elem.tag == 'currentStatus':
                currentStatus = elem.text
        return response
    
    def list_build_plans(self, projectKey = None):
        plans_conn = self.invoke("listBuildNames.action")

        response = {}

        name = None
        key = None

        for event, elem in xml.etree.ElementTree.iterparse(plans_conn):
            if elem.tag == 'name':
                name = elem.text
            elif elem.tag == 'key':
                key = elem.text
            elif elem.tag == 'build':
                enabled = False
                if elem.attrib['enabled'] == 'true':
                    enabled = True
                response[key] = { 'name': name, 'enabled': enabled }
                name = None
                key = None
        if projectKey is not None:
            return dict([(k, v) for k,v in response.items() if k.startswith(projectKey + "-")])
        return response

    def add_comment_to_build_results(self, buildKey, buildNumber, comment):
        conn = self.invoke("addCommentToBuildResults.action", 
                {
                    'buildKey': buildKey,
                    'buildNumber': buildNumber,
                    'content': comment
                })
        parser = xml.etree.ElementTree.parse(conn)
        return parser.find("response").text

    def add_label_to_build_results(self, buildKey, buildNumber, label):
        conn = self.invoke("addLabelToBuildResults.action", 
                {
                    'buildKey': buildKey,
                    'buildNumber': buildNumber,
                    'label': label
                })
        parser = xml.etree.ElementTree.parse(conn)
        return parser.find("response").text

    def get_latest_build_results(self, buildKey):
        conn = self.invoke("getLatestBuildResults.action",
                { "buildKey": buildKey })
        response = self.parse_build_results(conn)
        conn.close()
        if response.keys() != []:
            return response
        else:
            return None

    def get_recently_completed_build_results_for_build(self, buildKey):
        conn = self.invoke("getRecentlyCompletedBuildResultsForBuild.action",
                { "buildKey": buildKey })
        response = self.parse_build_results(conn)
        conn.close()
        return response

    def get_latest_build_results_for_project(self, projectKey):
        conn = self.invoke("getLatestBuildResultsForProject.action", 
                { 'projectKey': projectKey })
        response = self.parse_build_results(conn)
        conn.close()
        return response

    def get_recently_completed_build_results_for_project(self, projectKey):
        conn = self.invoke("getRecentlyCompletedBuildResultsForProject.action", 
                { 'projectKey': projectKey })
        response = self.parse_build_results(conn)
        conn.close()
        return response

    def get_latest_builds_by_user(self, username):
        conn = self.invoke("getLatestBuildsByUser.action",
                { 'username': username })
        response = self.parse_build_results(conn)
        conn.close()
        return response

    def get_artifact_path(self, buildKey, buildNumber, artifact):
        build_path_component = buildKey 
        if buildNumber == 'latest':
            build_path_component += "/latest"
        else:
            build_path_component += "-" + buildNumber
            
        artifact_url = self.bambooconf['bamboouri'] + build_path_component \
            + "/artifact/" + artifact

        return artifact_url

    def get_artifact(self, buildKey, buildNumber, artifact):
        artifact_url = self.get_artifact_path(buildKey, buildNumber, artifact)

        if verbose:
            print artifact_url
        conn = self.opener.open(artifact_url)
        data = conn.read()
        return data

    def parse_build_results(self, conn):
        response = {}
        build = {}
        for event, elem in xml.etree.ElementTree.iterparse(conn):

            if elem.tag == 'response' or elem.tag == 'build':
                if build != {}:
                    response[build['buildKey']] = build
                    build = {}
            elif elem.tag == 'commit':
                if not(build.has_key('commits')):
                    build['commits'] = []
                build['commits'].append(elem.attrib['author'])
            elif elem.tag == 'commits':
                pass
            else:
                build[elem.tag] = elem.text.strip()
        if build != {}:
            if response.keys().__contains__('buildKey'):
                response[build['buildKey']] = build
            elif response.keys().__contains__('key'):
                response[build['key']] = build
        return response


class RepoParser(HTMLParser.HTMLParser):
    def __init__(self):
        HTMLParser.HTMLParser.__init__(self)
        self.links = []
        self.binary_links = []
        self.prefix = ""
        self.type = ''

    def feed(self, link, data):
        old_prefix = self.prefix
        if self.prefix == '':
            self.prefix = "/".join([link, ""])
        else:
            self.prefix = "/".join([self.prefix, link, ""])
        HTMLParser.HTMLParser.feed(self, data)
        self.prefix = old_prefix

    def handle_starttag(self, tag, attrs):
        if tag.lower() == 'img':
            alt = [b for a, b in attrs if a == 'alt']
            if alt is not None:
                self.type = alt[0]

        if tag.lower() == 'a':
            [href] = [b for a, b in attrs if a == 'href']
            if href.startswith("..") or href.endswith("repoview"):
                return
            href = self.fix_broken_links(href)

            if self.type == '(file)':
                self.binary_links.append(self.prefix + href)
            else:
                self.links.append(self.prefix + href)

    def fix_broken_links(self, href):
        end_of_prefix = self.prefix.split("/")[-2]
        if href.startswith(end_of_prefix + "/"):
            href = href[len(end_of_prefix + "/"):]
        return href


def print_result_values(values, head = ''):
    if type(values) == type({}):
        for key in values.keys():
            print_result_values(values[key], key + ": ")
    else:
        print str(head) + str(values)

def print_results(results):
    if results is not None:
        if type(results) == dict:
            for key in results.keys():
                print "key: " + key
                print_result_values(results[key])
                print ""
        else:
            sys.stdout.write(results)

def mkdirtree(d):
    if not(os.path.exists(d)):
        parent = "/".join(d.split("/")[0:-1])
        if parent != "": 
            mkdirtree(parent)
        os.mkdir(d)
opts, args = getopt.getopt(sys.argv[1:], "Vv:d:faib")

version = destination = 'unset'
update_frags = False
update_api_docs = False
installers = True
binaries = True

for (option, value) in opts:
    if option == '-v':
        version = value
    elif option == '-V':
        verbose = True
    elif option == '-d':
        destination = value
    elif option == '-f':
        update_frags = True
    elif option == '-a':
        update_api_docs = True
    elif option == '-i':
        installers = False
    elif option == '-b':
        binaries = False


# Not ready yet
update_api_docs = False

if version == 'unset':
    print "Usage: " + sys.argv[0] + " -v VERSION [-d DESTINATION]"
    sys.exit(1)

default_dest = "/mcs/globus.org/ftppub"
if (destination is 'unset') and (os.stat(default_dest) is not None):
    destination = default_dest

# 5.2.0 has a separate project, others are tagged builds from 5.2.x project
if version == "5.2.0":
    bamboo_version = "GT520"
    build_kickoff = bamboo_version + "-BUILD52"
else:
    bamboo_version = "GT" + "".join(version.split(".")[0:2])
    build_kickoff = bamboo_version + "-BUILD" + "".join(version.split(".")[0:2])

client = BambooClient()

client.login()
build_plans = client.list_build_plans(bamboo_version)
plan_binaries = {}
build_results = {}
installer_plan = None

for plan in build_plans:
    if plan.find("-BUILD") != -1:
        continue
    if not(build_plans[plan]['enabled']):
        continue
    build_results[plan] = client.get_latest_build_results(plan).get(plan)
    buildKey = build_results[plan].get("buildKey");
    buildNumber = build_results[plan].get("buildNumber")

    artifacts = None
    print "Looking at plan " + plan
    if plan.endswith("-INSTALLER"):
        installer_plan = plan
        artifacts = client.get_artifact(buildKey, buildNumber, "installers")
    elif "-TESTSOURCE" in plan:
        try:
            artifacts = client.get_artifact(buildKey, buildNumber, "doxygen")
            continue
        except:
            print "Can't find doxygen for " + plan
            continue
    elif "-DEB" in plan or "-UBUNTU" in plan or "-RPM" in plan:
        artifacts = client.get_artifact(buildKey, buildNumber, "repo")
    else:
        continue
    html = RepoParser()
    html.feed("", artifacts)
    for l in html.links:
        html.feed(l, client.get_artifact(buildKey, buildNumber, l))

    plan_binaries[plan] = html.binary_links
    html.binary_links = []

# Create root of distribution point
[major, minor, point] = version.split(".")
destination_version_root = destination + \
    "/gt%s/%s.%s/%s.%s.%s" %(major, major, minor, major, minor, point)

for tree in ["installers/repo", "installers/src", "packages/rpm", "packages/deb", "packages/src"]:
    mkdirtree(destination_version_root + "/" + tree)

# Put source installer into place
if installers:
    for plan in plan_binaries:
        if plan.find("-INSTALLER") != -1:
            installer_files = [r for r in plan_binaries[plan] if "installer.tar.gz" in r]
            for inst in installer_files:
                data = client.get_artifact(build_results[plan].get("buildKey"),
                        build_results[plan].get("buildNumber"),
                        inst)
                f = file(destination_version_root + "/installers/src/" + \
                    os.path.basename(inst), "w")
                f.write(data)
                f.close()

# Put RPM and Debian repository definition packages into place
if binaries:
    for plan in plan_binaries:
        if plan.find("-RPM") != -1:
            repopackage = [r for r in plan_binaries[plan] \
                if r.find("Globus-repo-config") != -1 and r.find("SRPMS") == -1]
            data = client.get_artifact(build_results[plan].get("buildKey"),
                    build_results[plan].get("buildNumber"),
                    repopackage[0])
            f = file(destination_version_root + "/installers/repo/" + \
                os.path.basename(repopackage[0]), "w")
            f.write(data)
            f.close()
        elif plan.find("-DEB") != -1 or plan.find("-UBUNTU") != -1:
            repopackage = [r for r in plan_binaries[plan] \
                if r.find("globus-repository") != -1 and r.find(".deb") != -1]
            data = client.get_artifact(build_results[plan].get("buildKey"),
                    build_results[plan].get("buildNumber"),
                    repopackage[0])
            f = file(destination_version_root + "/installers/repo/" + \
                os.path.basename(repopackage[0]), "w")
            f.write(data)
            f.close()

# Copy binary packages and source tarballs into place
    copied_source_tarballs = False
    source_tarball_dir = destination_version_root + "/packages/src"
    for plan in plan_binaries:
        if plan.find("-RPM") != -1:
            for package in plan_binaries[plan]:
                data = client.get_artifact(build_results[plan].get("buildKey"),
                        build_results[plan].get("buildNumber"),
                        package)
                package_path = destination_version_root + "/packages/rpm/" + \
                    package.replace("/repo/", "", 1)
                mkdirtree(os.path.dirname(package_path))
                f = file(package_path, "w")
                f.write(data)
                f.close()

                if copied_source_tarballs is False and \
                        package.find(".src.rpm") != -1:
                    os.system("rpm2cpio %s | (cd %s ; cpio -i '*.tar.gz')" \
                        %(package_path, source_tarball_dir))
            copied_source_tarballs = True
        elif plan.find("-DEB") != -1 or plan.find("-UBUNTU") != -1:
            distro_artifact = client.get_artifact(
                build_results[plan].get("buildKey"),
                build_results[plan].get("buildNumber"),
                "repo/conf/distributions")

            for line in distro_artifact.split("\n"):
                if line.startswith("Codename: "):
                    codename = line.split(":")[1].strip()

            planos = None
            planosver = None
            if plan.find("-DEB") != -1:
                planos = "debian"
                planosver = plan[plan.find("-DEB")+4:]
            elif plan.find("-UBUNTU") != -1:
                planos = "ubuntu"
                planosver = plan[plan.find("-UBUNTU")+7:]
                planosver = planosver[0:2] + "." + planosver[2:4]
                
            plan_root = destination_version_root + "/packages/deb/" + planos + \
                "/" + planosver
            for package in plan_binaries[plan]:
                data = client.get_artifact(build_results[plan].get("buildKey"),
                        build_results[plan].get("buildNumber"),
                        package)
                package_path = plan_root + package.replace("/repo", "", 1)
                mkdirtree(os.path.dirname(package_path))
                f = file(package_path, "w")
                f.write(data)
                f.close()

# Copy frags into place
if update_frags:
    if installer_plan is None:
        print "Unable to copy frags into place"
    else:
        frag_co_dir = tempfile.mkdtemp()
        oldwd = os.getcwd()
        os.chdir(frag_co_dir)
        print "Storing frags in %s" %(frag_co_dir)

        docpoint = point
        if "rc" in point:
            docpoint = point[0:point.find("rc")]

        os.system("cvs -d cvs.globus.org:/home/globdev/CVS/alliance-web co -d docs alliance/toolkit/docs/%s.%s/%s.%s.%s" %(major, minor, major, minor, docpoint))

        frags = [f for f in plan_binaries[installer_plan] if f.endswith(".xml")]
        print "frags are %s" %(str(frags))
        for frag in frags:
            print "frag basename is " + os.path.basename(frag)
            for root, dirnames, filenames in os.walk(frag_co_dir):
                for filename in fnmatch.filter(filenames, os.path.basename(frag)):
                    destfile = os.path.join(root, filename)
                    print "matching filename is " + destfile
                    data = client.get_artifact(build_results[installer_plan].get("buildKey"),
                            build_results[installer_plan].get("buildNumber"),
                            frag)
                    
                    f = file(destfile, "w")
                    f.write(data)
                    f.close();
        #system("cvs commit docs")
        os.chdir(oldwd)
        #shutil.rmtree(frag_co_dir)


# Copy API docs into place
if update_api_docs:
    print "Trying to update API docs"
    for plan in build_plans:
        print "Looking at plan " + plan
        if "TESTSOURCE" in plan:
            print "Might be ok"
            if plan in plan_binaries:
                [apitar] = [(name) for name in plan_binaries[plan] if name.endswith(".tar.gz")]
                api_version_dir = os.path.join(apidir, "/api/c-" + version)
                os.mkdir(api_version_dir, 0755)
                data = client.get_artifact(build_results[plan].get("buildKey"),
                        build_results[plan].get("buildNumber"),
                        apitar)
                (fd, name) = tempfile.mkstemp()
                fd.write(data)
                fd.close()
                oldwd = os.getcwd()
                os.chdir(api_version_dir)
                tar = tarfile.TarFile(name)
                tar.extractall()
                os.remove(name)
