try:
# Using Psyco makes it about 25% faster, but there's a bug in psyco in
# handling of eval causing it to use unlimited memory with the magic
# file enabled.
#    import psyco
#    psyco.full()
#    from psyco.classes import *
    pass
except:
    pass
import re
import base64
import cStringIO
import specialuu
import array
import email.Utils
import zlib
import magic

# Comment out if you don't want magic detection
magicf = magic.MagicFile()

# Open our output file
outfile = open("gnats2bz_data.sql", "w")

# List of GNATS fields
fieldnames = ("Number", "Category", "Synopsis", "Confidential", "Severity",
              "Priority", "Responsible", "State", "Quarter", "Keywords",
              "Date-Required", "Class", "Submitter-Id", "Arrival-Date",
              "Closed-Date", "Last-Modified", "Originator", "Release",
              "Organization", "Environment", "Description", "How-To-Repeat",
              "Fix", "Release-Note", "Audit-Trail", "Unformatted")

# Dictionary telling us which GNATS fields are multiline
multilinefields = {"Organization":1, "Environment":1, "Description":1,
                   "How-To-Repeat":1, "Fix":1, "Release-Note":1,
                   "Audit-Trail":1, "Unformatted":1}

# Mapping of GCC release to version. Our version string is updated every
# so we need to funnel all release's with 3.4 in the string to be version
# 3.4 for bug tracking purposes
# The key is a regex to match, the value is the version it corresponds
# with
releasetovermap = {r"3\.4":"3.4", r"3\.3":"3.3", r"3\.2\.2":"3.2.2",
                   r"3\.2\.1":"3.2.1", r"3\.2":"3.2", r"3\.1\.2":"3.1.2",
                   r"3\.1\.1":"3.1.1", r"3\.1":"3.1", r"3\.0\.4":"3.0.4",
                   r"3\.0\.3":"3.0.3", r"3\.0\.2":"3.0.2", r"3\.0\.1":"3.0.1",
                   r"3\.0":"3.0", r"2\.95\.4":"2.95.4", r"2\.95\.3":"2.95.3",
                   r"2\.95\.2":"2.95.2", r"2\.95\.1":"2.95.1",
                   r"2\.95":"2.95", r"2\.97":"2.97",
                   r"2\.96.*[rR][eE][dD].*[hH][aA][tT]":"2.96 (redhat)",
                   r"2\.96":"2.96"}

# These map the field name to the field id bugzilla assigns. We need
# the id when doing bug activity.
fieldids = {"State":8, "Responsible":15}

# These are the keywords we use in gcc bug tracking. They are transformed
# into bugzilla keywords.  The format here is <keyword>-><bugzilla keyword id>
keywordids = {"wrong-code":1, "ice-on-legal-code":2, "ice-on-illegal-code":3,
              "rejects-legal":4, "accepts-illegal":5, "pessimizes-code":6}

# Map from GNATS states to Bugzilla states.  Duplicates and reopened bugs
# are handled when parsing the audit trail, so no need for them here.
state_lookup = {"":"NEW", "open":"ASSIGNED", "analyzed":"ASSIGNED",
                "feedback":"WAITING", "closed":"CLOSED",
                "suspended":"SUSPENDED"}

# Table of versions that exist in the bugs, built up as we go along
versions_table = {}

# Delimiter gnatsweb uses for attachments
attachment_delimiter = "----gnatsweb-attachment----\n"

# Here starts the various regular expressions we use
# Matches an entire GNATS single line field
gnatfieldre = re.compile(r"""^([>\w\-]+)\s*:\s*(.*)\s*$""")

# Matches the name of a GNATS field
fieldnamere = re.compile(r"""^>(.*)$""")

# Matches the useless part of an envelope
uselessre = re.compile(r"""^(\S*?):\s*""", re.MULTILINE)

# Matches the filename in a content disposition
dispositionre = re.compile("(\\S+);\\s*filename=\"([^\"]+)\"")

# Matches the last changed date in the entire text of a bug
# If you have other editable fields that get audit trail entries, modify this
# The field names are explicitly listed in order to speed up matching
lastdatere = re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-When: )(.+?)$""", re.MULTILINE)

# Matches the From line of an email or the first line of an audit trail entry
# We use this re to find the begin lines of all the audit trail entries
# The field names are explicitly listed in order to speed up matching
fromtore=re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-From-To: |From: )""", re.MULTILINE)

# These re's match the various parts of an audit trail entry
changedfromtore=re.compile(r"""^(\w+?)-Changed-From-To: (.+?)$""", re.MULTILINE)
changedbyre=re.compile(r"""^\w+?-Changed-By: (.+?)$""", re.MULTILINE)
changedwhenre=re.compile(r"""^\w+?-Changed-When: (.+?)$""", re.MULTILINE)
changedwhyre=re.compile(r"""^\w+?-Changed-Why:\s*(.*?)$""", re.MULTILINE)

# This re matches audit trail text saying that the current bug is a duplicate of another
duplicatere=re.compile(r"""(?:")?Dup(?:licate)?(?:d)?(?:")? of .*?(\d+)""", re.IGNORECASE | re.MULTILINE)

# Get the text of a From: line
fromre=re.compile(r"""^From: (.*?)$""", re.MULTILINE)

# Get the text of a Date: Line
datere=re.compile(r"""^Date: (.*?)$""", re.MULTILINE)

#  Map of the responsible file to email addresses
responsible_map = {}
#  List of records in the responsible file
responsible_list = []
#  List of records in the categories file
categories_list = []
# List of pr's in the index
pr_list = []
# Map usernames to user ids
usermapping = {}
# Start with this user id
userid_base = 2

# Name of gnats user
gnats_username = "gnats@gcc.gnu.org"
# Name of unassigned user
unassigned_username = "unassigned@gcc.gnu.org"

gnats_db_dir = "."
product = "gcc"
productdesc = "GNU Compiler Connection"
milestoneurl = "http://gcc/gnu.org"
defaultmilestone = "3.4"

def write_non_bug_tables():
    """ Write out the non-bug related tables, such as products, profiles, etc."""
    # Set all non-unconfirmed bugs's everconfirmed flag
    print >>outfile, "update bugs set everconfirmed=1 where bug_status != 'UNCONFIRMED';"

    # Set all bugs assigned to the unassigned user to NEW
    print >>outfile, "update bugs set bug_status='NEW',assigned_to='NULL' where bug_status='ASSIGNED' AND assigned_to=3;"
    
    # Insert the products
    print >>outfile, "\ninsert into products ("
    print >>outfile, "  product, description, milestoneurl, disallownew,"
    print >>outfile, "  defaultmilestone, votestoconfirm) values ("
    print >>outfile, "  '%s', '%s', '%s', 0, '%s', 1);" % (product,
                                                           productdesc,
                                                           milestoneurl,
                                                           defaultmilestone)

    # Insert the components    
    for category in categories_list:
        component = SqlQuote(category[0])
        productstr = SqlQuote(product)
        description = SqlQuote(category[1])
        initialowner = SqlQuote("3")
        print >>outfile, "\ninsert into components (";
        print >>outfile, "  value, program, initialowner, initialqacontact,"
        print >>outfile, "  description) values ("
        print >>outfile, "  %s, %s, %s, '', %s);" % (component, productstr,
                                                     initialowner, description)
        
    # Insert the versions
    for productstr, version_list in versions_table.items():
        productstr = SqlQuote(productstr)
        for version in version_list:
            version = SqlQuote(version)
            print >>outfile, "\ninsert into versions (value, program) "
            print >>outfile, "  values (%s, %s);" % (version, productstr)
            
    # Insert the users
    for username, userid in usermapping.items():
        realname = map_username_to_realname(username)
        username = SqlQuote(username)
        realname = SqlQuote(realname)
        print >>outfile, "\ninsert into profiles ("
        print >>outfile, "  userid, login_name, password, cryptpassword, realname, groupset"
        print >>outfile, ") values ("
        print >>outfile, "%s,%s,'password',encrypt('password'), %s, 0);" % (userid, username, realname)
    print >>outfile, "update profiles set groupset=1 << 32 where login_name like '%\@gcc.gnu.org';"
    
def unixdate2datetime(unixdate):
    """ Convert a unix date to a datetime value """
    year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate)
    return "%d-%02d-%02d %02d:%02d:%02d" % (year,month,day,hour,min,sec)

def unixdate2timestamp(unixdate):
    """ Convert a unix date to a timestamp value """
    year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate)
    return "%d%02d%02d%02d%02d%02d" % (year,month,day,hour,min,sec)

def SqlQuote(str):
    """ Perform SQL quoting on a string """
    return "'%s'" % str.replace("'", """''""").replace("\\", "\\\\").replace("\0","\\0")

def convert_gccver_to_ver(gccver):
    """ Given a gcc version, convert it to a Bugzilla version. """
    for k in releasetovermap.keys():
        if re.search(".*%s.*" % k, gccver) is not None:
            return releasetovermap[k]
    result = re.search(r""".*(\d\.\d) \d+ \(experimental\).*""", gccver)
    if result is not None:
        return result.group(1)
    return "unknown"

def load_index(fname):
    """ Load in the GNATS index file """
    global pr_list
    ifp = open(fname)
    for record in ifp.xreadlines():
        fields = record.split("|")
        pr_list.append(fields[0])
    ifp.close()
    
def load_categories(fname):
    """ Load in the GNATS categories file """
    global categories_list
    cfp = open(fname)
    for record in cfp.xreadlines():
        if re.search("^#", record) is not None:
            continue
        categories_list.append(record.split(":"))
    cfp.close()
    
def map_username_to_realname(username): 
    """ Given a username, find the real name """
    name = username
    name = re.sub("@.*", "", name)
    for responsible_record in responsible_list:
	if responsible_record[0] == name:
	    return responsible_record[1]
    if len(responsible_record) > 2:
        if responsible_record[2] == username:
	    return responsible_record[1]
    return ""


def get_userid(responsible):
    """ Given an email address, get the user id """
    global responsible_map
    global usermapping
    global userid_base
    if responsible is None:
        return -1
    responsible = responsible.lower()
    responsible = re.sub("sources.redhat.com", "gcc.gnu.org", responsible)
    if responsible_map.has_key(responsible):
        responsible = responsible_map[responsible]
    if usermapping.has_key(responsible):
        return usermapping[responsible]
    else:
        usermapping[responsible] = userid_base
        userid_base += 1
    return usermapping[responsible]

def load_responsible(fname):
    """ Load in the GNATS responsible file """
    global responsible_map
    global responsible_list
    rfp = open(fname)
    for record in rfp.xreadlines():
        if re.search("^#", record) is not None:
            continue
        split_record = record.split(":")
        responsible_map[split_record[0]] = split_record[2].rstrip()
        responsible_list.append(record.split(":"))
    rfp.close()

def split_csl(list):
    """ Split a comma separated list """
    newlist = re.split(r"""\s*,\s*""", list)
    return newlist

def fix_email_addrs(addrs):
    """ Perform various fixups and cleaning on an e-mail address """
    addrs = split_csl(addrs)
    trimmed_addrs = []
    for addr in addrs:
        addr = re.sub(r"""\(.*\)""","",addr)
        addr = re.sub(r""".*<(.*)>.*""","\\1",addr)
        addr = addr.rstrip()
        addr = addr.lstrip()
        trimmed_addrs.append(addr)
    addrs = ", ".join(trimmed_addrs)
    return addrs

class Bugzillabug(object):
    """ Class representing a bugzilla bug """
    def __init__(self, gbug):
        """ Initialize a bugzilla bug from a GNATS bug.  """
        self.bug_id = gbug.bug_id
        self.long_descs = []
        self.bug_ccs = [get_userid("gcc-bugs@gcc.gnu.org")]
        self.bug_activity = []
        self.attachments = gbug.attachments
        self.gnatsfields = gbug.fields
        self.need_unformatted = gbug.has_unformatted_attach == 0
        self.need_unformatted &= gbug.fields.has_key("Unformatted")
        self.translate_pr()
        self.update_versions()
        if self.fields.has_key("Audit-Trail"):
            self.parse_audit_trail()
            self.write_bug()
            
    def parse_fromto(type, string):
        """ Parses the from and to parts of a changed-from-to line """
        fromstr = ""
        tostr = ""

        # Some slightly messed up changed lines have unassigned-new,
        # instead of unassigned->new. So we make the > optional.        
        result = re.search(r"""(.*)-(?:>?)(.*)""", string)
        
        # Only know how to handle parsing of State and Responsible
        # changed-from-to right now
        if type == "State":
            fromstr = state_lookup[result.group(1)]
            tostr = state_lookup[result.group(2)]
        elif type == "Responsible":
            if result.group(1) != "":
                fromstr = result.group(1)
            if result.group(2) != "":
                tostr = result.group(2)
            if responsible_map.has_key(fromstr):
                fromstr = responsible_map[fromstr]
            if responsible_map.has_key(tostr):
                tostr = responsible_map[tostr]  
        return (fromstr, tostr)
    parse_fromto = staticmethod(parse_fromto)
    
    def parse_audit_trail(self):
        """ Parse a GNATS audit trail """
        trail = self.fields["Audit-Trail"]
        # Begin to split the audit trail into pieces
        result = fromtore.finditer(trail)
        starts = []
        ends = []
        pieces = []
        # Make a list of the pieces
        for x in result:
            pieces.append (x)
        # Find the start and end of each piece
        if len(pieces) > 0:
            for x in xrange(len(pieces)-1):
                starts.append(pieces[x].start())
                ends.append(pieces[x+1].start())
            starts.append(pieces[-1].start())
            ends.append(len(trail))
        pieces = []
        # Now make the list of actual text of the pieces
        for x in xrange(len(starts)):
            pieces.append(trail[starts[x]:ends[x]])
        # And parse the actual pieces
        for piece in pieces:
            result = changedfromtore.search(piece)
            # See what things we actually have inside this entry, and
            # handle them approriately
            if result is not None:
                type = result.group(1)
                changedfromto = result.group(2)
                # If the bug was reopened, mark it as such
                if changedfromto.find("closed->analyzed") != -1:
                    if self.fields["bug_status"] == "'NEW'":
                        self.fields["bug_status"] = "'REOPENED'"
                if type == "State" or type == "Responsible":
                    oldstate, newstate = self.parse_fromto (type, changedfromto)
                result = changedbyre.search(piece)
                if result is not None:
                    changedby = result.group(1)
                result = changedwhenre.search(piece)
                if result is not None:
                    changedwhen = result.group(1)
                    changedwhen = unixdate2datetime(changedwhen)
                    changedwhen = SqlQuote(changedwhen)
                result = changedwhyre.search(piece)
                changedwhy = piece[result.start(1):]
                #changedwhy = changedwhy.lstrip()
                changedwhy = changedwhy.rstrip()
                changedby = get_userid(changedby)
		# Put us on the cc list if we aren't there already
                if changedby != self.fields["userid"] \
                       and changedby not in self.bug_ccs:
                    self.bug_ccs.append(changedby)
                # If it's a duplicate, mark it as such
                result = duplicatere.search(changedwhy)
                if result is not None:
                    newtext = "*** This bug has been marked as a duplicate of %s ***" % result.group(1)
                    newtext = SqlQuote(newtext)
                    self.long_descs.append((self.bug_id, changedby,
                                            changedwhen, newtext))
                    self.fields["bug_status"] = "'RESOLVED'"
                    self.fields["resolution"] = "'DUPLICATE'"
                    self.fields["userid"] = changedby
                else:
                    newtext = "%s-Changed-From-To: %s\n%s-Changed-Why: %s\n" % (type, changedfromto, type, changedwhy)
                    newtext = SqlQuote(newtext)
                    self.long_descs.append((self.bug_id, changedby,
                                            changedwhen, newtext))
                if type == "State" or type == "Responsible":
                    newstate = SqlQuote("%s" % newstate)
                    oldstate = SqlQuote("%s" % oldstate)
                    fieldid = fieldids[type]
                    self.bug_activity.append((newstate, oldstate, fieldid, changedby, changedwhen))
                    
            else:
		# It's an email
                result = fromre.search(piece)
                if result is None:
                    continue
                fromstr = result.group(1)
                fromstr = fix_email_addrs(fromstr)
                fromstr = get_userid(fromstr)
                result = datere.search(piece)
                if result is None:
                    continue
                datestr = result.group(1)
                datestr = SqlQuote(unixdate2timestamp(datestr))
                if fromstr != self.fields["userid"] \
                       and fromstr not in self.bug_ccs:
                    self.bug_ccs.append(fromstr)
                self.long_descs.append((self.bug_id, fromstr, datestr,
                                        SqlQuote(piece)))
                
                    

    def write_bug(self):
	""" Output a bug to the data file """
        fields = self.fields
        print >>outfile, "\ninsert into bugs("
        print >>outfile, "  bug_id, assigned_to, bug_severity, priority, bug_status, creation_ts, delta_ts,"
        print >>outfile, "  short_desc,"
        print >>outfile, "  reporter, version,"
        print >>outfile, "  product, component, resolution, target_milestone, qa_contact,"
        print >>outfile, "  gccbuild, gcctarget, gcchost, keywords"
        print >>outfile, "  ) values ("
        print >>outfile, "%s, %s, %s, %s, %s, %s, %s," % (self.bug_id, fields["userid"], fields["bug_severity"], fields["priority"], fields["bug_status"], fields["creation_ts"], fields["delta_ts"])
        print >>outfile, "%s," % (fields["short_desc"])
        print >>outfile, "%s, %s," % (fields["reporter"], fields["version"])
        print >>outfile, "%s, %s, %s, %s, 0," %(fields["product"], fields["component"], fields["resolution"], fields["target_milestone"])
        print >>outfile, "%s, %s, %s, %s" % (fields["gccbuild"], fields["gcctarget"], fields["gcchost"], fields["keywords"])
        print >>outfile, ");"
        if self.fields["keywords"] != 0:
            print >>outfile, "\ninsert into keywords (bug_id, keywordid) values ("
            print >>outfile, " %s, %s);" % (self.bug_id, fields["keywordid"])
        for id, who, when, text in self.long_descs:
            print >>outfile, "\ninsert into longdescs ("
            print >>outfile, "  bug_id, who, bug_when, thetext) values("
            print >>outfile, "  %s, %s, %s, %s);" % (id, who, when, text)
        for name, data, who in self.attachments:
            print >>outfile, "\ninsert into attachments ("
            print >>outfile, "  bug_id, filename, description, mimetype, ispatch, submitter_id) values ("
	    ftype = None
	    # It's *magic*!
	    if name.endswith(".ii") == 1:
		ftype = "text/x-c++"
 	    elif name.endswith(".i") == 1:
		ftype = "text/x-c"
	    else:
		ftype = magicf.detect(cStringIO.StringIO(data))
            if ftype is None:
                ftype = "application/octet-stream"
            
            print >>outfile, "%s,%s,%s, %s,0, %s,%s);" %(self.bug_id, SqlQuote(name), SqlQuote(name), SqlQuote (ftype), who)
            print >>outfile, "\ninsert into attach_data ("
            print >>outfile, "\n(id, thedata) values (last_insert_id(),"
            print >>outfile, "%s);" % (SqlQuote(zlib.compress(data)))
        for newstate, oldstate, fieldid, changedby, changedwhen in self.bug_activity:
            print >>outfile, "\ninsert into bugs_activity ("
            print >>outfile, "  bug_id, who, bug_when, fieldid, added, removed) values ("
            print >>outfile, "  %s, %s, %s, %s, %s, %s);" % (self.bug_id,
                                                             changedby,
                                                             changedwhen,
                                                             fieldid,
                                                             newstate,
                                                             oldstate)
        for cc in self.bug_ccs:
            print >>outfile, "\ninsert into cc(bug_id, who) values (%s, %s);" %(self.bug_id, cc)
    def update_versions(self):
	""" Update the versions table to account for the version on this bug """
        global versions_table
        if self.fields.has_key("Release") == 0 \
               or self.fields.has_key("Category") == 0:
            return
        curr_product = "gcc"
        curr_version = self.fields["Release"]
        if curr_version == "":
            return
        curr_version = convert_gccver_to_ver (curr_version)
        if versions_table.has_key(curr_product) == 0:
            versions_table[curr_product] = []
        for version in versions_table[curr_product]:
            if version == curr_version:
                return
        versions_table[curr_product].append(curr_version)
    def translate_pr(self):
	""" Transform a GNATS PR into a Bugzilla bug """
        self.fields = self.gnatsfields
        if (self.fields.has_key("Organization") == 0) \
           or self.fields["Organization"].find("GCC"):
            self.fields["Originator"] = ""
            self.fields["Organization"] = ""
        self.fields["Organization"].lstrip()
        if (self.fields.has_key("Release") == 0) \
               or self.fields["Release"] == "" \
               or self.fields["Release"].find("unknown-1.0") != -1:
            self.fields["Release"]="unknown"
        if self.fields.has_key("Responsible"):
            result = re.search(r"""\w+""", self.fields["Responsible"])
            self.fields["Responsible"] = "%s%s" % (result.group(0), "@gcc.gnu.org")
        self.fields["gcchost"] = ""
        self.fields["gcctarget"] = ""
        self.fields["gccbuild"] = ""
        if self.fields.has_key("Environment"):
            result = re.search("^host: (.+?)$", self.fields["Environment"],
                               re.MULTILINE)
            if result is not None:
                self.fields["gcchost"] = result.group(1)
            result = re.search("^target: (.+?)$", self.fields["Environment"],
                               re.MULTILINE)
            if result is not None:
                self.fields["gcctarget"] = result.group(1)
            result = re.search("^build: (.+?)$", self.fields["Environment"],
                               re.MULTILINE)
            if result is not None:
                self.fields["gccbuild"] = result.group(1)
        self.fields["userid"] = get_userid(self.fields["Responsible"])
        self.fields["bug_severity"] = "normal"
        if self.fields["Class"] == "change-request":
            self.fields["bug_severity"] = "enhancement"
        elif self.fields.has_key("Severity"):
            if self.fields["Severity"] == "critical":
                self.fields["bug_severity"] = "critical"
            elif self.fields["Severity"] == "serious":
                self.fields["bug_severity"] = "major"
        elif self.fields.has_key("Synopsis"):
            if re.search("crash|assert", self.fields["Synopsis"]):
                self.fields["bug_severity"] = "critical"
            elif re.search("wrong|error", self.fields["Synopsis"]):
                self.fields["bug_severity"] = "major"
        self.fields["bug_severity"] = SqlQuote(self.fields["bug_severity"])
        self.fields["keywords"] = 0
        if keywordids.has_key(self.fields["Class"]):
            self.fields["keywords"] = self.fields["Class"]
            self.fields["keywordid"] = keywordids[self.fields["Class"]]
            self.fields["keywords"] = SqlQuote(self.fields["keywords"])
        self.fields["priority"] = "P1"
        if self.fields.has_key("Severity") and self.fields.has_key("Priority"):
            severity = self.fields["Severity"]
            priority = self.fields["Priority"]
            if severity == "critical":
                if priority == "high":
                    self.fields["priority"] = "P1"
                else:
                    self.fields["priority"] = "P2"
            elif severity == "serious":
                if priority == "low":
                    self.fields["priority"] = "P4"
                else:
                    self.fields["priority"] = "P3"
            else:
                if priority == "high":
                    self.fields["priority"] = "P4"
                else:
                    self.fields["priority"] = "P5"
        self.fields["priority"] = SqlQuote(self.fields["priority"])
        state = self.fields["State"]
        if (state == "open" or state == "analyzed") and self.fields["userid"] != 3:
            self.fields["bug_status"] = "ASSIGNED"
            self.fields["resolution"] = ""
        elif state == "feedback":
            self.fields["bug_status"] = "WAITING"
            self.fields["resolution"] = ""
        elif state == "closed":
            self.fields["bug_status"] = "CLOSED"
            if self.fields.has_key("Class"):
                theclass = self.fields["Class"]
                if theclass.find("duplicate") != -1:
                    self.fields["resolution"]="DUPLICATE"
                elif theclass.find("mistaken") != -1:
                    self.fields["resolution"]="INVALID"                    
                else:
                    self.fields["resolution"]="FIXED"
            else:
                self.fields["resolution"]="FIXED"
        elif state == "suspended":
            self.fields["bug_status"] = "SUSPENDED"
            self.fields["resolution"] = ""
        elif state == "analyzed" and self.fields["userid"] == 3:
            self.fields["bug_status"] = "NEW"
            self.fields["resolution"] = ""
        else:
            self.fields["bug_status"] = "UNCONFIRMED"
            self.fields["resolution"] = ""
        self.fields["bug_status"] = SqlQuote(self.fields["bug_status"])
        self.fields["resolution"] = SqlQuote(self.fields["resolution"])
        self.fields["creation_ts"] = ""
        if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "":
            self.fields["creation_ts"] = unixdate2datetime(self.fields["Arrival-Date"])
        self.fields["creation_ts"] = SqlQuote(self.fields["creation_ts"])
        self.fields["delta_ts"] = ""
        if self.fields.has_key("Audit-Trail"):
            result = lastdatere.findall(self.fields["Audit-Trail"])
            result.reverse()
            if len(result) > 0:
                self.fields["delta_ts"] = unixdate2timestamp(result[0])
        if self.fields["delta_ts"] == "":
            if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "":
                self.fields["delta_ts"] = unixdate2timestamp(self.fields["Arrival-Date"])
        self.fields["delta_ts"] = SqlQuote(self.fields["delta_ts"])
        self.fields["short_desc"] = SqlQuote(self.fields["Synopsis"])
        if self.fields.has_key("Reply-To") and self.fields["Reply-To"] != "":
            self.fields["reporter"] = get_userid(self.fields["Reply-To"])
        elif self.fields.has_key("Mail-Header"):
            result = re.search(r"""From .*?([\w.]+@[\w.]+)""", self.fields["Mail-Header"])
            if result:
                self.fields["reporter"] = get_userid(result.group(1))
            else:
                self.fields["reporter"] = get_userid(gnats_username)
        else:
            self.fields["reporter"] = get_userid(gnats_username)
        long_desc = self.fields["Description"]
        long_desc2 = ""
        for field in ["Release", "Environment", "How-To-Repeat"]:
            if self.fields.has_key(field) and self.fields[field] != "":
                long_desc += ("\n\n%s:\n" % field) + self.fields[field]
        if self.fields.has_key("Fix") and self.fields["Fix"] != "":
            long_desc2 = "Fix:\n" + self.fields["Fix"]
        if self.need_unformatted  == 1 and self.fields["Unformatted"] != "":
            long_desc += "\n\nUnformatted:\n" + self.fields["Unformatted"]
        if long_desc != "":
            self.long_descs.append((self.bug_id, self.fields["reporter"],
                                    self.fields["creation_ts"],
                                    SqlQuote(long_desc)))
        if long_desc2 != "":
            self.long_descs.append((self.bug_id, self.fields["reporter"],
                                    self.fields["creation_ts"],
                                    SqlQuote(long_desc2)))
        for field in ["gcchost", "gccbuild", "gcctarget"]:
            self.fields[field] = SqlQuote(self.fields[field])
        self.fields["version"] = ""
        if self.fields["Release"] != "":
            self.fields["version"] = convert_gccver_to_ver (self.fields["Release"])
        self.fields["version"] = SqlQuote(self.fields["version"])
        self.fields["product"] = SqlQuote("gcc")
        self.fields["component"] = "invalid"
        if self.fields.has_key("Category"):
            self.fields["component"] = self.fields["Category"]
        self.fields["component"] = SqlQuote(self.fields["component"])
        self.fields["target_milestone"] = "---"
        if self.fields["version"].find("3.4") != -1:
            self.fields["target_milestone"] = "3.4"
        self.fields["target_milestone"] = SqlQuote(self.fields["target_milestone"])
        if self.fields["userid"] == 2:
            self.fields["userid"] = "\'NULL\'"

class GNATSbug(object):
    """ Represents a single GNATS PR """
    def __init__(self, filename):
        self.attachments = []
        self.has_unformatted_attach = 0
        fp = open (filename)
        self.fields = self.parse_pr(fp.xreadlines())
        self.bug_id = int(self.fields["Number"])
        if self.fields.has_key("Unformatted"):
            self.find_gnatsweb_attachments()
        if self.fields.has_key("How-To-Repeat"):
            self.find_regular_attachments("How-To-Repeat")
        if self.fields.has_key("Fix"):
            self.find_regular_attachments("Fix")

    def get_attacher(fields):
        if fields.has_key("Reply-To") and fields["Reply-To"] != "":
            return get_userid(fields["Reply-To"])
        else:
            result = None
            if fields.has_key("Mail-Header"):
                result = re.search(r"""From .*?([\w.]+\@[\w.]+)""",
                                   fields["Mail-Header"])
            if result is not None:
                reporter = get_userid(result.group(1))
            else:
                reporter = get_userid(gnats_username)
    get_attacher = staticmethod(get_attacher)
    def find_regular_attachments(self, which):
        fields = self.fields
        while re.search("^begin [0-7]{3}", fields[which],
                        re.DOTALL | re.MULTILINE):
            outfp = cStringIO.StringIO()
            infp = cStringIO.StringIO(fields[which])
            filename, start, end = specialuu.decode(infp, outfp, quiet=0)
            fields[which]=fields[which].replace(fields[which][start:end],
                                                "See attachments for %s\n" % filename)
            self.attachments.append((filename, outfp.getvalue(),
                                     self.get_attacher(fields)))

    def decode_gnatsweb_attachment(self, attachment):
        result = re.split(r"""\n\n""", attachment, 1)
        if len(result) == 1:
            return -1
        envelope, body = result
        envelope = uselessre.split(envelope)
        envelope.pop(0)
        # Turn the list of key, value into a dict of key => value
        attachinfo = dict([(envelope[i], envelope[i+1]) for i in xrange(0,len(envelope),2)])
        for x in attachinfo.keys():
            attachinfo[x] = attachinfo[x].rstrip()
        if (attachinfo.has_key("Content-Type") == 0) or \
           (attachinfo.has_key("Content-Disposition") == 0):
            raise ValueError, "Unable to parse file attachment"
        result = dispositionre.search(attachinfo["Content-Disposition"])
        filename = result.group(2)
        filename = re.sub(".*/","", filename)
        filename = re.sub(".*\\\\","", filename)
        attachinfo["filename"]=filename
        result = re.search("""(\S+);.*""", attachinfo["Content-Type"])
        if result is not None:
            attachinfo["Content-Type"] = result.group(1)
        if attachinfo.has_key("Content-Transfer-Encoding"):
            if attachinfo["Content-Transfer-Encoding"] == "base64":
                attachinfo["data"] = base64.decodestring(body)
        else:
            attachinfo["data"]=body

        return (attachinfo["filename"], attachinfo["data"],
                self.get_attacher(self.fields))

    def find_gnatsweb_attachments(self):
        fields = self.fields
        attachments = re.split(attachment_delimiter, fields["Unformatted"])
        fields["Unformatted"] = attachments.pop(0)
        for attachment in attachments:
            result = self.decode_gnatsweb_attachment (attachment)
            if result != -1:
                self.attachments.append(result)
            self.has_unformatted_attach = 1
    def parse_pr(lines):
        #fields = {"envelope":[]}
        fields = {"envelope":array.array("c")}
        hdrmulti = "envelope"
        for line in lines:
            line = line.rstrip('\n')
            line += '\n'
            result = gnatfieldre.search(line)
            if result is None:
                if hdrmulti != "":
                    if fields.has_key(hdrmulti):
                        #fields[hdrmulti].append(line)
                        fields[hdrmulti].fromstring(line)
                    else:
                        #fields[hdrmulti] = [line]
                        fields[hdrmulti] = array.array("c", line)
                continue
            hdr, arg = result.groups()
            ghdr = "*not valid*"
            result = fieldnamere.search(hdr)
            if result != None:
                ghdr = result.groups()[0]
            if ghdr in fieldnames:
                if multilinefields.has_key(ghdr):
                    hdrmulti = ghdr
                    #fields[ghdr] = [""]
                    fields[ghdr] = array.array("c")
                else:
                    hdrmulti = ""
                    #fields[ghdr] = [arg]
                    fields[ghdr] = array.array("c", arg)
            elif hdrmulti != "":
                #fields[hdrmulti].append(line)
                fields[hdrmulti].fromstring(line)
            if hdrmulti == "envelope" and \
               (hdr == "Reply-To" or hdr == "From" \
                or hdr == "X-GNATS-Notify"):
                arg = fix_email_addrs(arg)
                #fields[hdr] = [arg]
                fields[hdr] = array.array("c", arg)
	if fields.has_key("Reply-To") and len(fields["Reply-To"]) > 0:
            fields["Reply-To"] = fields["Reply-To"]
        else:
            fields["Reply-To"] = fields["From"]
        if fields.has_key("From"):
            del fields["From"]
        if fields.has_key("X-GNATS-Notify") == 0:
            fields["X-GNATS-Notify"] = array.array("c")
            #fields["X-GNATS-Notify"] = ""
        for x in fields.keys():
            fields[x] = fields[x].tostring()
            #fields[x] = "".join(fields[x])            
        for x in fields.keys():
            if multilinefields.has_key(x):
                fields[x] = fields[x].rstrip()

        return fields
    parse_pr = staticmethod(parse_pr)
load_index("%s/gnats-adm/index" % gnats_db_dir)
load_categories("%s/gnats-adm/categories" % gnats_db_dir)
load_responsible("%s/gnats-adm/responsible" % gnats_db_dir)
get_userid(gnats_username)
get_userid(unassigned_username)
for x in pr_list:
    print "Processing %s..." % x
    a = GNATSbug ("%s/%s" % (gnats_db_dir, x))
    b = Bugzillabug(a)
write_non_bug_tables()
outfile.close()
