#
# debianbts.py - Routines to deal with the debbugs web pages
#
#   Written by Chris Lawrence <lawrencc@debian.org>
#   (C) 1999-2000 Chris Lawrence
#
# This program is freely distributable per the following license:
#
##  Permission to use, copy, modify, and distribute this software and its
##  documentation for any purpose and without fee is hereby granted,
##  provided that the above copyright notice appears in all copies and that
##  both that copyright notice and this permission notice appear in
##  supporting documentation.
##
##  I DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
##  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL I
##  BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
##  DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
##  WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
##  ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
##  SOFTWARE.
#
# Version ##VERSION##; see changelog for revision history

import sgmllib, urllib, string, reportbug

ldap = None
try:
    import ldap

    if not ldap.has_key(LDAPError):
        ldap = None
except:
    pass

# URL opener that raises an exception for unfound pages and
# 401 Authorization Required
class OurURLopener(urllib.FancyURLopener):
    def __init__(self, *args):
        apply(urllib.FancyURLopener.__init__, (self,) + args)

    # Default http error handler: close the connection and raises IOError
    def http_error_default(self, url, fp, errcode, errmsg, headers):
        void = fp.read()
        fp.close()
        raise IOError, ('http error', errcode, errmsg, headers)

# Shortcut for basic usage
_urlopener = None
def urlopen(url, data=None, proxies=None):
    global _urlopener
    if not _urlopener:
        _urlopener = OurURLopener(proxies)
    if data is None:
        return _urlopener.open(url)
    else:
        return _urlopener.open(url, data)

# For summary pages, we want to keep:
#
# - Contents of <title>...</title>
# - Contents of <h2>...</h2>
# - Contents of each <li>
#
# For individual bugs, we want to keep:
# - Contents of <title>...</title>
# - Contents of final <pre>...</pre>

class BTSParser(sgmllib.SGMLParser):
    def __init__(self, mode='summary'):
        sgmllib.SGMLParser.__init__(self)
        self.hierarchy = []
        self.lidata = None
        self.lidatalist = None
        self.savedata = None
        self.title = None
        self.bugcount = 0
        self.mode = mode

    # --- Formatter interface, taking care of 'savedata' mode;
    # shouldn't need to be overridden

    def handle_data(self, data):
        if self.savedata is not None:
            self.savedata = self.savedata + data

    # --- Hooks to save data; shouldn't need to be overridden

    def save_bgn(self):
        self.savedata = ''

    def save_end(self, mode=0):
        data = self.savedata
        self.savedata = None
        if not mode: data = string.join(string.split(data))
        return data

    def check_li(self):
        if self.mode == 'summary':
            data = self.save_end()
            if data:
                self.lidatalist.append(data)
                self.bugcount = self.bugcount + 1

            self.lidata = 0

    def start_title(self, attrs):
        self.save_bgn()

    def end_title(self):
        self.title = self.save_end()

    def start_h2(self, attrs):
        if self.lidata: self.check_li()

        self.save_bgn()

    def end_h2(self):
        if self.mode == 'summary':
            self.hierarchy.append( (self.save_end(), []) )

    def do_br(self, attrs):
        if self.lidata and self.mode == 'summary': self.check_li()
        
    def do_li(self, attrs):
        if self.mode == 'summary':
            if self.lidata: self.check_li()

            self.lidata = 1
            self.lidatalist = self.hierarchy[-1][1]
            self.save_bgn()

    def start_pre(self, attrs):
        self.preblock = ''
        self.save_bgn()

    def end_pre(self):
        self.preblock = self.save_end(1)

def get_btsroot(system, mirrors=None):
    if mirrors:
        alternates = reportbug.SYSTEMS[system]['mirrors']
        for mirror in mirrors:
            if alternates.has_key(mirror):
                return alternates[mirror]
    return reportbug.SYSTEMS[system]['btsroot']

def package_url(system, package, mirrors=None):
    btsroot=get_btsroot(system, mirrors)
    return btsroot+('db/pa/l%s.html' % package) 

def report_url(system, number, mirrors=None):
    number = str(number)
    if len(number) < 2: raise TypeError
    btsroot=get_btsroot(system, mirrors)
    return btsroot+('db/%s/%s.html' % (number[:2], number))

def get_reports(package, system='debian', ldap_ok=1, mirrors=None,
                http_proxy=''):
    if reportbug.SYSTEMS[system].has_key('ldap') and ldap and ldap_ok:
        return get_ldap_reports(package, system) or \
               get_reports(package, system, 0, mirrors, http_proxy)

    proxies = urllib.getproxies()
    if http_proxy:
        proxies['http'] = http_proxy
        
    package = string.lower(package)
    url = package_url(system, package, mirrors)
    try:
        page = urlopen(url, proxies=proxies)
    except IOError, data:
        if data and data[0] == 'http error' and data[1] == 404:
            return (0, None, None)
        else:
            raise
        
    parser = BTSParser()
    parser.feed(page.read())
    parser.close()

    return parser.bugcount, parser.title, parser.hierarchy

def get_report(number, system='debian', ldap_ok=1, mirrors=None,
               http_proxy=''):
    if reportbug.SYSTEMS[system].has_key('ldap') and ldap and ldap_ok:
        return get_ldap_report(number, system) or \
               get_report(number, system, 0, mirrors, http_proxy)
        
    proxies = urllib.getproxies()
    if http_proxy:
        proxies['http'] = http_proxy
        
    url = report_url(system, number, mirrors)
    try:
        page = urlopen(url, proxies=proxies)
    except IOError, data:
        if data and data[0] == 'http error' and data[1] == 404:
            return None
        else:
            raise
        
    parser = BTSParser()
    parser.feed(page.read())
    parser.close()

    stuff = parser.preblock
    stuff = string.join(string.split(stuff, '\n\n')[1:], '\n\n')

    while stuff[-1]=='\n': stuff = stuff[:-1]

    return parser.title, stuff+'\n'

ldap_conn = None

def category_name(report):
    if report['severity'][0] == 'wishlist':
        str = 'Wishlist items - '
    else:
        str = string.capitalize(report['severity'][0]) + ' bugs - '

    if report.has_key('forwarded'):
        return str + 'forwarded to upstream software authors:'
    elif report.has_key('done'):
        return str + 'resolved:'
    else:
        return str + 'outstanding:'

def comparebugs(bug1, bug2):
    try:
        pos1, pos2 = reportbug.SEVLIST.index(bug1['severity'][0]),\
                     reportbug.SEVLIST.index(bug2['severity'][0])
    except ValueError:
        pos1, pos2 = 0, 0
    
    return (cmp(bug1.has_key('done'), bug2.has_key('done')) or
            cmp(bug1.has_key('forwarded'), bug2.has_key('forwarded')) or
            cmp(pos1, pos2) or
            cmp(int(bug1['bugid'][0]), int(bug2['bugid'][0])))

def get_ldap_reports(package, system='debian'):
    try:
        (server, port, qinfo) = reportbug.SYSTEMS[system]['ldap']

        if not ldap_conn:
            global ldap_conn
            ldap_conn = q = ldap.open(server, port)
        else:
            q = ldap_conn

        reports = q.search_s(qinfo, ldap.SCOPE_SUBTREE,
                             ('(package=%s)' % package))
        if not reports: return 0, None, None

        # strip entry info
        reports = map(lambda x: x[1], reports)
        reports.sort(comparebugs)
        hierarchy = []
        curcat = ''
        for report in reports:
            cat = category_name(report)
            if cat != curcat:
                hierarchy.append([cat, []])
                curcat = cat

            if report.has_key('subject'):
                info = '#%s: %s' % (report['bugid'][0], report['subject'][0])
            else:
                info = '#%s: no subject' % (report['bugid'][0])
            hierarchy[-1][1].append(info)

        return len(reports), 'Reports on '+package, hierarchy
    except (ldap.LDAPError, SystemError):
        return None

def get_ldap_report(number, system='debian'):
    try:
        (server, port, qinfo) = reportbug.SYSTEMS[system]['ldap']

        if not ldap_conn:
            global ldap_conn
            ldap_conn = q = ldap.open(server, port)
        else:
            q = ldap_conn

        number = int(number)
        info = q.search_s(qinfo, ldap.SCOPE_SUBTREE, ('(bugid=%d)' % number),
                          ['report', 'subject', 'date', 'originater'])
        info = info[0]
        if not info[1]:
            return None

        if info[1].has_key('subject'):
            subject = '#%d: %s' % (number, info[1]['subject'][0])
        else:
            subject = '#%d: no subject' % (number)
        report = info[1]['report'][0]
        stuff = string.join(string.split(report, '\n\n')[1:], '\n\n')

        while stuff[-1]=='\n': stuff = stuff[:-1]

        if info[1].has_key('date'):
            stuff = 'Date: '+info[1]['date'][0]+'\n'+stuff
        if info[1].has_key('originater'):
            stuff = 'From: '+info[1]['originater'][0]+'\n'+stuff
        return subject, stuff+'\n'
    except (ldap.LDAPError, SystemError):
        return None

def close_ldap_conn():
    global ldap_conn
    if ldap_conn:
        ldap_conn.unbind()
        ldap_conn = None
