]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/queue.py
Implement rdeps checking in cruft-report
[dak.git] / daklib / queue.py
index 3ab87246d1601f1fc5dd52912293deb155f95b38..d0772276f20158f9ce3206d319ad3857222c5c8d 100755 (executable)
@@ -51,10 +51,10 @@ from holding import Holding
 from urgencylog import UrgencyLog
 from dbconn import *
 from summarystats import SummaryStats
-from utils import parse_changes, check_dsc_files
+from utils import parse_changes, check_dsc_files, build_package_list
 from textutils import fix_maintainer
-from binary import Binary
 from lintian import parse_lintian_output, generate_reject_messages
+from contents import UnpackedSource
 
 ###############################################################################
 
@@ -77,7 +77,11 @@ def get_type(f, session):
         file_type = f["dbtype"]
     elif re_source_ext.match(f["type"]):
         file_type = "dsc"
+    elif f['architecture'] == 'source' and f["type"] == 'unreadable':
+        utils.warn('unreadable source file (will continue and hope for the best)')
+        return f["type"]
     else:
+        file_type = f["type"]
         utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
 
     # Validate the override type
@@ -91,10 +95,13 @@ def get_type(f, session):
 
 # Determine what parts in a .changes are NEW
 
-def determine_new(changes, files, warn=1):
+def determine_new(filename, changes, files, warn=1, session = None, dsc = None, new = None):
     """
     Determine what parts in a C{changes} file are NEW.
 
+    @type filename: str
+    @param filename: changes filename
+
     @type changes: Upload.Pkg.changes dict
     @param changes: Changes dictionary
 
@@ -104,19 +111,39 @@ def determine_new(changes, files, warn=1):
     @type warn: bool
     @param warn: Warn if overrides are added for (old)stable
 
+    @type dsc: Upload.Pkg.dsc dict
+    @param dsc: (optional); Dsc dictionary
+
+    @type new: dict
+    @param new: new packages as returned by a previous call to this function, but override information may have changed
+
     @rtype: dict
     @return: dictionary of NEW components.
 
     """
-    new = {}
-
-    session = DBConn().session()
+    # TODO: This should all use the database instead of parsing the changes
+    # file again
+    byhand = {}
+    if new is None:
+        new = {}
+
+    dbchg = get_dbchange(filename, session)
+    if dbchg is None:
+        print "Warning: cannot find changes file in database; won't check byhand"
+
+    # Try to get the Package-Set field from an included .dsc file (if possible).
+    if dsc:
+        for package, entry in build_package_list(dsc, session).items():
+            if package not in new:
+                new[package] = entry
 
     # Build up a list of potentially new things
     for name, f in files.items():
-        # Skip byhand elements
-#        if f["type"] == "byhand":
-#            continue
+        # Keep a record of byhand elements
+        if f["section"] == "byhand":
+            byhand[name] = 1
+            continue
+
         pkg = f["package"]
         priority = f["priority"]
         section = f["section"]
@@ -151,19 +178,40 @@ def determine_new(changes, files, warn=1):
     # Fix up the list of target suites
     cnf = Config()
     for suite in changes["suite"].keys():
-        override = cnf.Find("Suite::%s::OverrideSuite" % (suite))
-        if override:
-            (olderr, newerr) = (get_suite(suite, session) == None,
-                                get_suite(override, session) == None)
-            if olderr or newerr:
-                (oinv, newinv) = ("", "")
-                if olderr: oinv = "invalid "
-                if newerr: ninv = "invalid "
-                print "warning: overriding %ssuite %s to %ssuite %s" % (
-                        oinv, suite, ninv, override)
-            del changes["suite"][suite]
-            changes["suite"][override] = 1
+        oldsuite = get_suite(suite, session)
+        if not oldsuite:
+            print "WARNING: Invalid suite %s found" % suite
+            continue
+
+        if oldsuite.overridesuite:
+            newsuite = get_suite(oldsuite.overridesuite, session)
+
+            if newsuite:
+                print "INFORMATION: Using overrides from suite %s instead of suite %s" % (
+                    oldsuite.overridesuite, suite)
+                del changes["suite"][suite]
+                changes["suite"][oldsuite.overridesuite] = 1
+            else:
+                print "WARNING: Told to use overridesuite %s for %s but it doesn't exist.  Bugger" % (
+                    oldsuite.overridesuite, suite)
+
+    # Check for unprocessed byhand files
+    if dbchg is not None:
+        for b in byhand.keys():
+            # Find the file entry in the database
+            found = False
+            for f in dbchg.files:
+                if f.filename == b:
+                    found = True
+                    # If it's processed, we can ignore it
+                    if f.processed:
+                        del byhand[b]
+                    break
+
+            if not found:
+                print "Warning: Couldn't find BYHAND item %s in the database; assuming unprocessed"
 
+    # Check for new stuff
     for suite in changes["suite"].keys():
         for pkg in new.keys():
             ql = get_override(pkg, suite, new[pkg]["component"], new[pkg]["type"], session)
@@ -181,13 +229,11 @@ def determine_new(changes, files, warn=1):
             if new[pkg].has_key("othercomponents"):
                 print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"])
 
-    session.close()
-
-    return new
+    return new, byhand
 
 ################################################################################
 
-def check_valid(new):
+def check_valid(new, session = None):
     """
     Check if section and priority for NEW packages exist in database.
     Additionally does sanity checks:
@@ -204,13 +250,13 @@ def check_valid(new):
         priority_name = new[pkg]["priority"]
         file_type = new[pkg]["type"]
 
-        section = get_section(section_name)
+        section = get_section(section_name, session)
         if section is None:
             new[pkg]["section id"] = -1
         else:
             new[pkg]["section id"] = section.section_id
 
-        priority = get_priority(priority_name)
+        priority = get_priority(priority_name, session)
         if priority is None:
             new[pkg]["priority id"] = -1
         else:
@@ -242,14 +288,143 @@ class TarTime(object):
         self.future_files = {}
         self.ancient_files = {}
 
-    def callback(self, Kind, Name, Link, Mode, UID, GID, Size, MTime, Major, Minor):
-        if MTime > self.future_cutoff:
-            self.future_files[Name] = MTime
-        if MTime < self.past_cutoff:
-            self.ancient_files[Name] = MTime
+    def callback(self, member, data):
+        if member.mtime > self.future_cutoff:
+            self.future_files[Name] = member.mtime
+        if member.mtime < self.past_cutoff:
+            self.ancient_files[Name] = member.mtime
 
 ###############################################################################
 
+def prod_maintainer(notes, upload):
+    cnf = Config()
+
+    # Here we prepare an editor and get them ready to prod...
+    (fd, temp_filename) = utils.temp_filename()
+    temp_file = os.fdopen(fd, 'w')
+    for note in notes:
+        temp_file.write(note.comment)
+    temp_file.close()
+    editor = os.environ.get("EDITOR","vi")
+    answer = 'E'
+    while answer == 'E':
+        os.system("%s %s" % (editor, temp_filename))
+        temp_fh = utils.open_file(temp_filename)
+        prod_message = "".join(temp_fh.readlines())
+        temp_fh.close()
+        print "Prod message:"
+        print utils.prefix_multi_line_string(prod_message,"  ",include_blank_lines=1)
+        prompt = "[P]rod, Edit, Abandon, Quit ?"
+        answer = "XXX"
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = re_default_answer.search(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+    os.unlink(temp_filename)
+    if answer == 'A':
+        return
+    elif answer == 'Q':
+        end()
+        sys.exit(0)
+    # Otherwise, do the proding...
+    user_email_address = utils.whoami() + " <%s>" % (
+        cnf["Dinstall::MyAdminAddress"])
+
+    Subst = upload.Subst
+
+    Subst["__FROM_ADDRESS__"] = user_email_address
+    Subst["__PROD_MESSAGE__"] = prod_message
+    Subst["__CC__"] = "Cc: " + cnf["Dinstall::MyEmailAddress"]
+
+    prod_mail_message = utils.TemplateSubst(
+        Subst,cnf["Dir::Templates"]+"/process-new.prod")
+
+    # Send the prod mail
+    utils.send_mail(prod_mail_message)
+
+    print "Sent prodding message"
+
+################################################################################
+
+def edit_note(note, upload, session, trainee=False):
+    # Write the current data to a temporary file
+    (fd, temp_filename) = utils.temp_filename()
+    editor = os.environ.get("EDITOR","vi")
+    answer = 'E'
+    while answer == 'E':
+        os.system("%s %s" % (editor, temp_filename))
+        temp_file = utils.open_file(temp_filename)
+        newnote = temp_file.read().rstrip()
+        temp_file.close()
+        print "New Note:"
+        print utils.prefix_multi_line_string(newnote,"  ")
+        prompt = "[D]one, Edit, Abandon, Quit ?"
+        answer = "XXX"
+        while prompt.find(answer) == -1:
+            answer = utils.our_raw_input(prompt)
+            m = re_default_answer.search(prompt)
+            if answer == "":
+                answer = m.group(1)
+            answer = answer[:1].upper()
+    os.unlink(temp_filename)
+    if answer == 'A':
+        return
+    elif answer == 'Q':
+        end()
+        sys.exit(0)
+
+    comment = NewComment()
+    comment.package = upload.pkg.changes["source"]
+    comment.version = upload.pkg.changes["version"]
+    comment.comment = newnote
+    comment.author  = utils.whoami()
+    comment.trainee = trainee
+    session.add(comment)
+    session.commit()
+
+###############################################################################
+
+# FIXME: Should move into the database
+# suite names DMs can upload to
+dm_suites = ['unstable', 'experimental', 'squeeze-backports']
+
+def get_newest_source(source, session):
+    'returns the newest DBSource object in dm_suites'
+    ## the most recent version of the package uploaded to unstable or
+    ## experimental includes the field "DM-Upload-Allowed: yes" in the source
+    ## section of its control file
+    q = session.query(DBSource).filter_by(source = source). \
+        filter(DBSource.suites.any(Suite.suite_name.in_(dm_suites))). \
+        order_by(desc('source.version'))
+    return q.first()
+
+def get_suite_version_by_source(source, session):
+    'returns a list of tuples (suite_name, version) for source package'
+    q = session.query(Suite.suite_name, DBSource.version). \
+        join(Suite.sources).filter_by(source = source)
+    return q.all()
+
+def get_source_by_package_and_suite(package, suite_name, session):
+    '''
+    returns a DBSource query filtered by DBBinary.package and this package's
+    suite_name
+    '''
+    return session.query(DBSource). \
+        join(DBSource.binaries).filter_by(package = package). \
+        join(DBBinary.suites).filter_by(suite_name = suite_name)
+
+def get_suite_version_by_package(package, arch_string, session):
+    '''
+    returns a list of tuples (suite_name, version) for binary package and
+    arch_string
+    '''
+    return session.query(Suite.suite_name, DBBinary.version). \
+        join(Suite.binaries).filter_by(package = package). \
+        join(DBBinary.architecture). \
+        filter(Architecture.arch_string.in_([arch_string, 'all'])).all()
+
 class Upload(object):
     """
     Everything that has to do with an upload processed.
@@ -269,7 +444,8 @@ class Upload(object):
         cnf = Config()
         self.Subst = {}
         self.Subst["__ADMIN_ADDRESS__"] = cnf["Dinstall::MyAdminAddress"]
-        self.Subst["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
+        if cnf.has_key("Dinstall::BugServer"):
+            self.Subst["__BUG_SERVER__"] = cnf["Dinstall::BugServer"]
         self.Subst["__DISTRO__"] = cnf["Dinstall::MyDistribution"]
         self.Subst["__DAK_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
 
@@ -332,8 +508,18 @@ class Upload(object):
             self.Subst["__MAINTAINER_TO__"] = self.pkg.changes["maintainer2047"]
             self.Subst["__MAINTAINER__"] = self.pkg.changes.get("maintainer", "Unknown")
 
-        if "sponsoremail" in self.pkg.changes:
-            self.Subst["__MAINTAINER_TO__"] += ", %s" % self.pkg.changes["sponsoremail"]
+        # Process policy doesn't set the fingerprint field and I don't want to make it
+        # do it for now as I don't want to have to deal with the case where we accepted
+        # the package into PU-NEW, but the fingerprint has gone away from the keyring in
+        # the meantime so the package will be remarked as rejectable.  Urgh.
+        # TODO: Fix this properly
+        if self.pkg.changes.has_key('fingerprint'):
+            session = DBConn().session()
+            fpr = get_fingerprint(self.pkg.changes['fingerprint'], session)
+            if fpr and self.check_if_upload_is_sponsored("%s@debian.org" % fpr.uid.uid, fpr.uid.name):
+                if self.pkg.changes.has_key("sponsoremail"):
+                    self.Subst["__MAINTAINER_TO__"] += ", %s" % self.pkg.changes["sponsoremail"]
+            session.close()
 
         if cnf.has_key("Dinstall::TrackingServer") and self.pkg.changes.has_key("source"):
             self.Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (self.pkg.changes["source"], cnf["Dinstall::TrackingServer"])
@@ -346,6 +532,7 @@ class Upload(object):
         self.Subst["__REJECT_MESSAGE__"] = self.package_info()
         self.Subst["__SOURCE__"] = self.pkg.changes.get("source", "Unknown")
         self.Subst["__VERSION__"] = self.pkg.changes.get("version", "Unknown")
+        self.Subst["__SUITE__"] = ", ".join(self.pkg.changes["distribution"])
 
     ###########################################################################
     def load_changes(self, filename):
@@ -371,7 +558,7 @@ class Upload(object):
         except CantOpenError:
             self.rejects.append("%s: can't read file." % (filename))
             return False
-        except ParseChangesError, line:
+        except ParseChangesError as line:
             self.rejects.append("%s: parse error, can't grok: %s." % (filename, line))
             return False
         except ChangesUnicodeError:
@@ -381,10 +568,10 @@ class Upload(object):
         # Parse the Files field from the .changes into another dictionary
         try:
             self.pkg.files.update(utils.build_file_list(self.pkg.changes))
-        except ParseChangesError, line:
+        except ParseChangesError as line:
             self.rejects.append("%s: parse error, can't grok: %s." % (filename, line))
             return False
-        except UnknownFormatError, format:
+        except UnknownFormatError as format:
             self.rejects.append("%s: unknown format '%s'." % (filename, format))
             return False
 
@@ -422,7 +609,7 @@ class Upload(object):
              self.pkg.changes["maintainername"],
              self.pkg.changes["maintaineremail"]) = \
                    fix_maintainer (self.pkg.changes["maintainer"])
-        except ParseMaintError, msg:
+        except ParseMaintError as msg:
             self.rejects.append("%s: Maintainer field ('%s') failed to parse: %s" \
                    % (filename, self.pkg.changes["maintainer"], msg))
 
@@ -433,7 +620,7 @@ class Upload(object):
              self.pkg.changes["changedbyname"],
              self.pkg.changes["changedbyemail"]) = \
                    fix_maintainer (self.pkg.changes.get("changed-by", ""))
-        except ParseMaintError, msg:
+        except ParseMaintError as msg:
             self.pkg.changes["changedby822"] = ""
             self.pkg.changes["changedby2047"] = ""
             self.pkg.changes["changedbyname"] = ""
@@ -468,7 +655,7 @@ class Upload(object):
         Cnf = Config()
 
         # Handle suite mappings
-        for m in Cnf.ValueList("SuiteMappings"):
+        for m in Cnf.value_list("SuiteMappings"):
             args = m.split()
             mtype = args[0]
             if mtype == "map" or mtype == "silent-map":
@@ -514,7 +701,7 @@ class Upload(object):
 
         # Ensure target distributions exist
         for suite in self.pkg.changes["distribution"].keys():
-            if not Cnf.has_key("Suite::%s" % (suite)):
+            if not get_suite(suite.lower()):
                 self.rejects.append("Unknown distribution `%s'." % (suite))
 
     ###########################################################################
@@ -526,51 +713,43 @@ class Upload(object):
         # Extract package control information
         deb_file = utils.open_file(f)
         try:
-            control = apt_pkg.ParseSection(apt_inst.debExtractControl(deb_file))
+            control = apt_pkg.TagSection(utils.deb_extract_control(deb_file))
         except:
-            self.rejects.append("%s: debExtractControl() raised %s." % (f, sys.exc_type))
+            self.rejects.append("%s: deb_extract_control() raised %s." % (f, sys.exc_info()[0]))
             deb_file.close()
             # Can't continue, none of the checks on control would work.
             return
 
-        # Check for mandantory "Description:"
-        deb_file.seek(0)
-        try:
-            apt_pkg.ParseSection(apt_inst.debExtractControl(deb_file))["Description"] + '\n'
-        except:
-            self.rejects.append("%s: Missing Description in binary package" % (f))
-            return
-
         deb_file.close()
 
         # Check for mandatory fields
-        for field in [ "Package", "Architecture", "Version" ]:
-            if control.Find(field) == None:
+        for field in [ "Package", "Architecture", "Version", "Description" ]:
+            if field not in control:
                 # Can't continue
                 self.rejects.append("%s: No %s field in control." % (f, field))
                 return
 
         # Ensure the package name matches the one give in the .changes
-        if not self.pkg.changes["binary"].has_key(control.Find("Package", "")):
-            self.rejects.append("%s: control file lists name as `%s', which isn't in changes file." % (f, control.Find("Package", "")))
+        if not self.pkg.changes["binary"].has_key(control.find("Package", "")):
+            self.rejects.append("%s: control file lists name as `%s', which isn't in changes file." % (f, control.find("Package", "")))
 
         # Validate the package field
-        package = control.Find("Package")
+        package = control["Package"]
         if not re_valid_pkg_name.match(package):
             self.rejects.append("%s: invalid package name '%s'." % (f, package))
 
         # Validate the version field
-        version = control.Find("Version")
+        version = control["Version"]
         if not re_valid_version.match(version):
             self.rejects.append("%s: invalid version number '%s'." % (f, version))
 
         # Ensure the architecture of the .deb is one we know about.
-        default_suite = cnf.get("Dinstall::DefaultSuite", "Unstable")
-        architecture = control.Find("Architecture")
+        default_suite = cnf.get("Dinstall::DefaultSuite", "unstable")
+        architecture = control["Architecture"]
         upload_suite = self.pkg.changes["distribution"].keys()[0]
 
-        if      architecture not in [a.arch_string for a in get_suite_architectures(default_suite, session)] \
-            and architecture not in [a.arch_string for a in get_suite_architectures(upload_suite, session)]:
+        if      architecture not in [a.arch_string for a in get_suite_architectures(default_suite, session = session)] \
+            and architecture not in [a.arch_string for a in get_suite_architectures(upload_suite, session = session)]:
             self.rejects.append("Unknown architecture '%s'." % (architecture))
 
         # Ensure the architecture of the .deb is one of the ones
@@ -579,13 +758,13 @@ class Upload(object):
             self.rejects.append("%s: control file lists arch as `%s', which isn't in changes file." % (f, architecture))
 
         # Sanity-check the Depends field
-        depends = control.Find("Depends")
+        depends = control.find("Depends")
         if depends == '':
             self.rejects.append("%s: Depends field is empty." % (f))
 
         # Sanity-check the Provides field
-        provides = control.Find("Provides")
-        if provides:
+        provides = control.find("Provides")
+        if provides is not None:
             provide = re_spacestrip.sub('', provides)
             if provide == '':
                 self.rejects.append("%s: Provides field is empty." % (f))
@@ -594,20 +773,44 @@ class Upload(object):
                 if not re_valid_pkg_name.match(prov):
                     self.rejects.append("%s: Invalid Provides field content %s." % (f, prov))
 
+        # If there is a Built-Using field, we need to check we can find the
+        # exact source version
+        built_using = control.find("Built-Using")
+        if built_using is not None:
+            try:
+                entry["built-using"] = []
+                for dep in apt_pkg.parse_depends(built_using):
+                    bu_s, bu_v, bu_e = dep[0]
+                    # Check that it's an exact match dependency and we have
+                    # some form of version
+                    if bu_e != "=" or len(bu_v) < 1:
+                        self.rejects.append("%s: Built-Using contains non strict dependency (%s %s %s)" % (f, bu_s, bu_e, bu_v))
+                    else:
+                        # Find the source id for this version
+                        bu_so = get_sources_from_name(bu_s, version=bu_v, session = session)
+                        if len(bu_so) != 1:
+                            self.rejects.append("%s: Built-Using (%s = %s): Cannot find source package" % (f, bu_s, bu_v))
+                        else:
+                            entry["built-using"].append( (bu_so[0].source, bu_so[0].version, ) )
+
+            except ValueError as e:
+                self.rejects.append("%s: Cannot parse Built-Using field: %s" % (f, str(e)))
+
+
         # Check the section & priority match those given in the .changes (non-fatal)
-        if     control.Find("Section") and entry["section"] != "" \
-           and entry["section"] != control.Find("Section"):
+        if control.find("Section") and entry["section"] != "" \
+           and entry["section"] != control.find("Section"):
             self.warnings.append("%s control file lists section as `%s', but changes file has `%s'." % \
-                                (f, control.Find("Section", ""), entry["section"]))
-        if control.Find("Priority") and entry["priority"] != "" \
-           and entry["priority"] != control.Find("Priority"):
+                                (f, control.find("Section", ""), entry["section"]))
+        if control.find("Priority") and entry["priority"] != "" \
+           and entry["priority"] != control.find("Priority"):
             self.warnings.append("%s control file lists priority as `%s', but changes file has `%s'." % \
-                                (f, control.Find("Priority", ""), entry["priority"]))
+                                (f, control.find("Priority", ""), entry["priority"]))
 
         entry["package"] = package
         entry["architecture"] = architecture
         entry["version"] = version
-        entry["maintainer"] = control.Find("Maintainer", "")
+        entry["maintainer"] = control.find("Maintainer", "")
 
         if f.endswith(".udeb"):
             self.pkg.files[f]["dbtype"] = "udeb"
@@ -616,7 +819,7 @@ class Upload(object):
         else:
             self.rejects.append("%s is neither a .deb or a .udeb." % (f))
 
-        entry["source"] = control.Find("Source", entry["package"])
+        entry["source"] = control.find("Source", entry["package"])
 
         # Get the source version
         source = entry["source"]
@@ -641,7 +844,7 @@ class Upload(object):
         if entry["package"] != file_package:
             self.rejects.append("%s: package part of filename (%s) does not match package name in the %s (%s)." % \
                                 (f, file_package, entry["dbtype"], entry["package"]))
-        epochless_version = re_no_epoch.sub('', control.Find("Version"))
+        epochless_version = re_no_epoch.sub('', control.find("Version"))
 
         #  version
         file_version = m.group(2)
@@ -664,19 +867,27 @@ class Upload(object):
                                     (source_version, f, self.pkg.changes["version"]))
         else:
             # Check in the SQL database
-            if not source_exists(source_package, source_version, self.pkg.changes["distribution"].keys(), session):
+            if not source_exists(source_package, source_version, suites = \
+                self.pkg.changes["distribution"].keys(), session = session):
                 # Check in one of the other directories
                 source_epochless_version = re_no_epoch.sub('', source_version)
                 dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
-                if os.path.exists(os.path.join(cnf["Dir::Queue::Byhand"], dsc_filename)):
+
+                byhand_dir = get_policy_queue('byhand', session).path
+                new_dir = get_policy_queue('new', session).path
+
+                if os.path.exists(os.path.join(byhand_dir, dsc_filename)):
                     entry["byhand"] = 1
-                elif os.path.exists(os.path.join(cnf["Dir::Queue::New"], dsc_filename)):
+                elif os.path.exists(os.path.join(new_dir, dsc_filename)):
                     entry["new"] = 1
                 else:
                     dsc_file_exists = False
-                    for myq in ["Embargoed", "Unembargoed", "ProposedUpdates", "OldProposedUpdates"]:
-                        if cnf.has_key("Dir::Queue::%s" % (myq)):
-                            if os.path.exists(os.path.join(cnf["Dir::Queue::" + myq], dsc_filename)):
+                    # TODO: Don't hardcode this list: use all relevant queues
+                    #       The question is how to determine what is relevant
+                    for queue_name in ["embargoed", "unembargoed", "proposedupdates", "oldproposedupdates"]:
+                        queue = get_policy_queue(queue_name, session)
+                        if queue:
+                            if os.path.exists(os.path.join(queue.path, dsc_filename)):
                                 dsc_file_exists = True
                                 break
 
@@ -686,13 +897,6 @@ class Upload(object):
         # Check the version and for file overwrites
         self.check_binary_against_db(f, session)
 
-        # Temporarily disable contents generation until we change the table storage layout
-        #b = Binary(f)
-        #b.scan_package()
-        #if len(b.rejects) > 0:
-        #    for j in b.rejects:
-        #        self.rejects.append(j)
-
     def source_file_checks(self, f, session):
         entry = self.pkg.files[f]
 
@@ -750,15 +954,14 @@ class Upload(object):
             return
 
         # Handle component mappings
-        for m in cnf.ValueList("ComponentMappings"):
+        for m in cnf.value_list("ComponentMappings"):
             (source, dest) = m.split()
             if entry["component"] == source:
                 entry["original component"] = source
                 entry["component"] = dest
 
         # Ensure the component is valid for the target suite
-        if cnf.has_key("Suite:%s::Components" % (suite)) and \
-           entry["component"] not in cnf.ValueList("Suite::%s::Components" % (suite)):
+        if entry["component"] not in get_component_names(session):
             self.rejects.append("unknown component `%s' for suite `%s'." % (entry["component"], suite))
             return
 
@@ -779,7 +982,7 @@ class Upload(object):
         location = cnf["Dir::Pool"]
         l = get_location(location, entry["component"], session=session)
         if l is None:
-            self.rejects.append("[INTERNAL ERROR] couldn't determine location (Component: %)" % entry["component"])
+            self.rejects.append("[INTERNAL ERROR] couldn't determine location (Component: %s)" % entry["component"])
             entry["location id"] = -1
         else:
             entry["location id"] = l.location_id
@@ -802,9 +1005,11 @@ class Upload(object):
 
         # Check for packages that have moved from one component to another
         entry['suite'] = suite
-        res = get_binary_components(self.pkg.files[f]['package'], suite, entry["architecture"], session)
-        if res.rowcount > 0:
-            entry["othercomponents"] = res.fetchone()[0]
+        arch_list = [entry["architecture"], 'all']
+        component = get_component_by_package_suite(self.pkg.files[f]['package'], \
+            [suite], arch_list = arch_list, session = session)
+        if component is not None:
+            entry["othercomponents"] = component
 
     def check_files(self, action=True):
         file_keys = self.pkg.files.keys()
@@ -834,7 +1039,7 @@ class Upload(object):
                    or (dbc.in_queue is not None
                        and dbc.in_queue.queue_name not in ["unchecked", "newstage"]):
                 self.rejects.append("%s file already known to dak" % base_filename)
-        except NoResultFound, e:
+        except NoResultFound as e:
             # not known, good
             pass
 
@@ -843,10 +1048,11 @@ class Upload(object):
 
         for f, entry in self.pkg.files.items():
             # Ensure the file does not already exist in one of the accepted directories
-            for d in [ "Byhand", "New", "ProposedUpdates", "OldProposedUpdates", "Embargoed", "Unembargoed" ]:
-                if not cnf.has_key("Dir::Queue::%s" % (d)): continue
-                if os.path.exists(os.path.join(cnf["Dir::Queue::%s" % (d) ], f)):
-                    self.rejects.append("%s file already exists in the %s directory." % (f, d))
+            # TODO: Dynamically generate this list
+            for queue_name in [ "byhand", "new", "proposedupdates", "oldproposedupdates", "embargoed", "unembargoed" ]:
+                queue = get_policy_queue(queue_name, session)
+                if queue and os.path.exists(os.path.join(queue.path, f)):
+                    self.rejects.append("%s file already exists in the %s queue." % (f, queue_name))
 
             if not re_taint_free.match(f):
                 self.rejects.append("!!WARNING!! tainted filename: '%s'." % (f))
@@ -902,44 +1108,83 @@ class Upload(object):
             if not has_source:
                 self.rejects.append("no source found and Architecture line in changes mention source.")
 
-            if not has_binaries and cnf.FindB("Dinstall::Reject::NoSourceOnly"):
+            if (not has_binaries) and (not cnf.find_b("Dinstall::AllowSourceOnlyUploads")):
                 self.rejects.append("source only uploads are not supported.")
 
     ###########################################################################
-    def check_dsc(self, action=True, session=None):
-        """Returns bool indicating whether or not the source changes are valid"""
-        # Ensure there is source to check
-        if not self.pkg.changes["architecture"].has_key("source"):
-            return True
 
-        # Find the .dsc
+    def __dsc_filename(self):
+        """
+        Returns: (Status, Dsc_Filename)
+        where
+          Status: Boolean; True when there was no error, False otherwise
+          Dsc_Filename: String; name of the dsc file if Status is True, reason for the error otherwise
+        """
         dsc_filename = None
-        for f, entry in self.pkg.files.items():
-            if entry["type"] == "dsc":
+
+        # find the dsc
+        for name, entry in self.pkg.files.items():
+            if entry.has_key("type") and entry["type"] == "dsc":
                 if dsc_filename:
-                    self.rejects.append("can not process a .changes file with multiple .dsc's.")
-                    return False
+                    return False, "cannot process a .changes file with multiple .dsc's."
                 else:
-                    dsc_filename = f
+                    dsc_filename = name
 
-        # If there isn't one, we have nothing to do. (We have reject()ed the upload already)
         if not dsc_filename:
-            self.rejects.append("source uploads must contain a dsc file")
-            return False
+            return False, "source uploads must contain a dsc file"
+
+        return True, dsc_filename
+
+    def load_dsc(self, action=True, signing_rules=1):
+        """
+        Find and load the dsc from self.pkg.files into self.dsc
+
+        Returns: (Status, Reason)
+        where
+          Status: Boolean; True when there was no error, False otherwise
+          Reason: String; When Status is False this describes the error
+        """
+
+        # find the dsc
+        (status, dsc_filename) = self.__dsc_filename()
+        if not status:
+            # If status is false, dsc_filename has the reason
+            return False, dsc_filename
 
-        # Parse the .dsc file
         try:
-            self.pkg.dsc.update(utils.parse_changes(dsc_filename, signing_rules=1, dsc_file=1))
+            self.pkg.dsc.update(utils.parse_changes(dsc_filename, signing_rules=signing_rules, dsc_file=1))
         except CantOpenError:
-            # if not -n copy_to_holding() will have done this for us...
             if not action:
-                self.rejects.append("%s: can't read file." % (dsc_filename))
-        except ParseChangesError, line:
-            self.rejects.append("%s: parse error, can't grok: %s." % (dsc_filename, line))
-        except InvalidDscError, line:
-            self.rejects.append("%s: syntax error on line %s." % (dsc_filename, line))
+                return False, "%s: can't read file." % (dsc_filename)
+        except ParseChangesError as line:
+            return False, "%s: parse error, can't grok: %s." % (dsc_filename, line)
+        except InvalidDscError as line:
+            return False, "%s: syntax error on line %s." % (dsc_filename, line)
         except ChangesUnicodeError:
-            self.rejects.append("%s: dsc file not proper utf-8." % (dsc_filename))
+            return False, "%s: dsc file not proper utf-8." % (dsc_filename)
+
+        return True, None
+
+    ###########################################################################
+
+    def check_dsc(self, action=True, session=None):
+        """Returns bool indicating whether or not the source changes are valid"""
+        # Ensure there is source to check
+        if not self.pkg.changes["architecture"].has_key("source"):
+            return True
+
+        if session is None:
+            session = DBConn().session()
+
+        (status, reason) = self.load_dsc(action=action)
+        if not status:
+            self.rejects.append(reason)
+            return False
+        (status, dsc_filename) = self.__dsc_filename()
+        if not status:
+            # If status is false, dsc_filename has the reason
+            self.rejects.append(dsc_filename)
+            return False
 
         # Build up the file list of files mentioned by the .dsc
         try:
@@ -947,10 +1192,10 @@ class Upload(object):
         except NoFilesFieldError:
             self.rejects.append("%s: no Files: field." % (dsc_filename))
             return False
-        except UnknownFormatError, format:
+        except UnknownFormatError as format:
             self.rejects.append("%s: unknown format '%s'." % (dsc_filename, format))
             return False
-        except ParseChangesError, line:
+        except ParseChangesError as line:
             self.rejects.append("%s: parse error, can't grok: %s." % (dsc_filename, line))
             return False
 
@@ -968,7 +1213,11 @@ class Upload(object):
 
         # Only a limited list of source formats are allowed in each suite
         for dist in self.pkg.changes["distribution"].keys():
-            allowed = [ x.format_name for x in get_suite_src_formats(dist, session) ]
+            suite = get_suite(dist, session=session)
+            if not suite:
+                self.rejects.append("%s: cannot find suite %s when checking source formats" % (dsc_filename, dist))
+                continue
+            allowed = [ x.format_name for x in suite.srcformats ]
             if self.pkg.dsc["format"] not in allowed:
                 self.rejects.append("%s: source format '%s' not allowed in %s (accepted: %s) " % (dsc_filename, self.pkg.dsc["format"], dist, ", ".join(allowed)))
 
@@ -976,7 +1225,7 @@ class Upload(object):
         try:
             # We ignore the return value
             fix_maintainer(self.pkg.dsc["maintainer"])
-        except ParseMaintError, msg:
+        except ParseMaintError as msg:
             self.rejects.append("%s: Maintainer field ('%s') failed to parse: %s" \
                                  % (dsc_filename, self.pkg.dsc["maintainer"], msg))
 
@@ -986,7 +1235,7 @@ class Upload(object):
             if field:
                 # Have apt try to parse them...
                 try:
-                    apt_pkg.ParseSrcDepends(field)
+                    apt_pkg.parse_src_depends(field)
                 except:
                     self.rejects.append("%s: invalid %s field (can not be parsed by apt)." % (dsc_filename, field_name.title()))
 
@@ -1004,13 +1253,26 @@ class Upload(object):
         session = DBConn().session()
         self.check_source_against_db(dsc_filename, session)
         self.check_dsc_against_db(dsc_filename, session)
-        session.close()
+
+        dbchg = get_dbchange(self.pkg.changes_file, session)
 
         # Finally, check if we're missing any files
         for f in self.later_check_files:
-            self.rejects.append("Could not find file %s references in changes" % f)
+            print 'XXX: %s' % f
+            # Check if we've already processed this file if we have a dbchg object
+            ok = False
+            if dbchg:
+                for pf in dbchg.files:
+                    if pf.filename == f and pf.processed:
+                        self.notes.append('%s was already processed so we can go ahead' % f)
+                        ok = True
+                        del self.pkg.files[f]
+            if not ok:
+                self.rejects.append("Could not find file %s references in changes" % f)
 
-        return True
+        session.close()
+
+        return (len(self.rejects) == 0)
 
     ###########################################################################
 
@@ -1054,14 +1316,13 @@ class Upload(object):
             os.symlink(self.pkg.orig_files[orig_file]["path"], dest)
 
         # Extract the source
-        cmd = "dpkg-source -sn -x %s" % (dsc_filename)
-        (result, output) = commands.getstatusoutput(cmd)
-        if (result != 0):
-            self.rejects.append("'dpkg-source -x' failed for %s [return code: %s]." % (dsc_filename, result))
-            self.rejects.append(utils.prefix_multi_line_string(output, " [dpkg-source output:] "))
+        try:
+            unpacked = UnpackedSource(dsc_filename)
+        except Exception as e:
+            self.rejects.append("'dpkg-source -x' failed for %s. (%s)" % (dsc_filename, str(e)))
             return
 
-        if not cnf.Find("Dir::Queue::BTSVersionTrack"):
+        if not cnf.find("Dir::BTSVersionTrack"):
             return
 
         # Get the upstream version
@@ -1070,19 +1331,19 @@ class Upload(object):
             upstr_version = re_strip_revision.sub('', upstr_version)
 
         # Ensure the changelog file exists
-        changelog_filename = "%s-%s/debian/changelog" % (self.pkg.dsc["source"], upstr_version)
-        if not os.path.exists(changelog_filename):
+        changelog_file = unpacked.get_changelog_file()
+        if changelog_file is None:
             self.rejects.append("%s: debian/changelog not found in extracted source." % (dsc_filename))
             return
 
         # Parse the changelog
         self.pkg.dsc["bts changelog"] = ""
-        changelog_file = utils.open_file(changelog_filename)
         for line in changelog_file.readlines():
             m = re_changelog_versions.match(line)
             if m:
                 self.pkg.dsc["bts changelog"] += line
         changelog_file.close()
+        unpacked.cleanup()
 
         # Check we found at least one revision in the changelog
         if not self.pkg.dsc["bts changelog"]:
@@ -1108,7 +1369,7 @@ class Upload(object):
 
         try:
             shutil.rmtree(tmpdir)
-        except OSError, e:
+        except OSError as e:
             if e.errno != errno.EACCES:
                 print "foobar"
                 utils.fubar("%s: couldn't remove tmp dir for source tree." % (self.pkg.dsc["source"]))
@@ -1121,7 +1382,7 @@ class Upload(object):
             if result != 0:
                 utils.fubar("'%s' failed with result %s." % (cmd, result))
             shutil.rmtree(tmpdir)
-        except Exception, e:
+        except Exception as e:
             print "foobar2 (%s)" % e
             utils.fubar("%s: couldn't remove tmp dir for source tree." % (self.pkg.dsc["source"]))
 
@@ -1241,16 +1502,16 @@ class Upload(object):
                 continue
 
             # Look in some other queues for the file
-            queues = ('New', 'Byhand', 'ProposedUpdates',
-                'OldProposedUpdates', 'Embargoed', 'Unembargoed')
+            queue_names = ['new', 'byhand',
+                           'proposedupdates', 'oldproposedupdates',
+                           'embargoed', 'unembargoed']
 
-            for queue in queues:
-                if not cnf.get('Dir::Queue::%s' % queue):
+            for queue_name in queue_names:
+                queue = get_policy_queue(queue_name, session)
+                if not queue:
                     continue
 
-                queuefile_path = os.path.join(
-                    cnf['Dir::Queue::%s' % queue], filename
-                )
+                queuefile_path = os.path.join(queue.path, filename)
 
                 if not os.path.exists(queuefile_path):
                     # Does not exist in this queue
@@ -1284,7 +1545,7 @@ class Upload(object):
 
         # If we do not have a tagfile, don't do anything
         tagfile = cnf.get("Dinstall::LintianTags")
-        if tagfile is None:
+        if not tagfile:
             return
 
         # Parse the yaml file
@@ -1294,7 +1555,7 @@ class Upload(object):
 
         try:
             lintiantags = yaml.load(sourcecontent)['lintian']
-        except yaml.YAMLError, msg:
+        except yaml.YAMLError as msg:
             utils.fubar("Can not read the lintian tags file %s, YAML error: %s." % (tagfile, msg))
             return
 
@@ -1345,7 +1606,7 @@ class Upload(object):
             if not self.pkg.changes.has_key("urgency"):
                 self.pkg.changes["urgency"] = cnf["Urgency::Default"]
             self.pkg.changes["urgency"] = self.pkg.changes["urgency"].lower()
-            if self.pkg.changes["urgency"] not in cnf.ValueList("Urgency::Valid"):
+            if self.pkg.changes["urgency"] not in cnf.value_list("Urgency::Valid"):
                 self.warnings.append("%s is not a valid urgency; it will be treated as %s by testing." % \
                                      (self.pkg.changes["urgency"], cnf["Urgency::Default"]))
                 self.pkg.changes["urgency"] = cnf["Urgency::Default"]
@@ -1367,19 +1628,8 @@ class Upload(object):
             if entry["type"] == "deb":
                 tar.reset()
                 try:
-                    deb_file = utils.open_file(filename)
-                    apt_inst.debExtract(deb_file, tar.callback, "control.tar.gz")
-                    deb_file.seek(0)
-                    try:
-                        apt_inst.debExtract(deb_file, tar.callback, "data.tar.gz")
-                    except SystemError, e:
-                        # If we can't find a data.tar.gz, look for data.tar.bz2 instead.
-                        if not re.search(r"Cannot f[ui]nd chunk data.tar.gz$", str(e)):
-                            raise
-                        deb_file.seek(0)
-                        apt_inst.debExtract(deb_file,tar.callback,"data.tar.bz2")
-
-                    deb_file.close()
+                    deb = apt_inst.DebFile(filename)
+                    deb.control.go(tar.callback)
 
                     future_files = tar.future_files.keys()
                     if future_files:
@@ -1397,9 +1647,13 @@ class Upload(object):
                         self.rejects.append("%s: has %s file(s) with a time stamp too ancient (e.g. %s [%s])."
                                % (filename, num_ancient_files, ancient_file, time.ctime(ancient_date)))
                 except:
-                    self.rejects.append("%s: deb contents timestamp check failed [%s: %s]" % (filename, sys.exc_type, sys.exc_value))
+                    self.rejects.append("%s: deb contents timestamp check failed [%s: %s]" % (filename, sys.exc_info()[0], sys.exc_info()[1]))
 
     def check_if_upload_is_sponsored(self, uid_email, uid_name):
+        for key in "maintaineremail", "changedbyemail", "maintainername", "changedbyname":
+            if not self.pkg.changes.has_key(key):
+                return False
+        uid_email = '@'.join(uid_email.split('@')[:2])
         if uid_email in [self.pkg.changes["maintaineremail"], self.pkg.changes["changedbyemail"]]:
             sponsored = False
         elif uid_name in [self.pkg.changes["maintainername"], self.pkg.changes["changedbyname"]]:
@@ -1408,8 +1662,12 @@ class Upload(object):
                 sponsored = True
         else:
             sponsored = True
+            sponsor_addresses = utils.gpg_get_key_addresses(self.pkg.changes["fingerprint"])
+            debian_emails = filter(lambda addr: addr.endswith('@debian.org'), sponsor_addresses)
+            if uid_email not in debian_emails:
+                if debian_emails:
+                    uid_email = debian_emails[0]
             if ("source" in self.pkg.changes["architecture"] and uid_email and utils.is_email_alias(uid_email)):
-                sponsor_addresses = utils.gpg_get_key_addresses(self.pkg.changes["fingerprint"])
                 if (self.pkg.changes["maintaineremail"] not in sponsor_addresses and
                     self.pkg.changes["changedbyemail"] not in sponsor_addresses):
                         self.pkg.changes["sponsoremail"] = uid_email
@@ -1451,22 +1709,22 @@ class Upload(object):
         # Check any one-off upload blocks
         self.check_upload_blocks(fpr, session)
 
-        # Start with DM as a special case
+        # If the source_acl is None, source is never allowed
+        if fpr.source_acl is None:
+            if self.pkg.changes["architecture"].has_key("source"):
+                rej = 'Fingerprint %s may not upload source' % fpr.fingerprint
+                rej += '\nPlease contact ftpmaster if you think this is incorrect'
+                self.rejects.append(rej)
+                return
+        # Do DM as a special case
         # DM is a special case unfortunately, so we check it first
         # (keys with no source access get more access than DMs in one
         #  way; DMs can only upload for their packages whether source
         #  or binary, whereas keys with no access might be able to
         #  upload some binaries)
-        if fpr.source_acl.access_level == 'dm':
+        elif fpr.source_acl.access_level == 'dm':
             self.check_dm_upload(fpr, session)
         else:
-            # Check source-based permissions for other types
-            if self.pkg.changes["architecture"].has_key("source") and \
-                fpr.source_acl.access_level is None:
-                rej = 'Fingerprint %s may not upload source' % fpr.fingerprint
-                rej += '\nPlease contact ftpmaster if you think this is incorrect'
-                self.rejects.append(rej)
-                return
             # If not a DM, we allow full upload rights
             uid_email = "%s@debian.org" % (fpr.uid.uid)
             self.check_if_upload_is_sponsored(uid_email, fpr.uid.name)
@@ -1488,8 +1746,11 @@ class Upload(object):
 
         if len(tmparches.keys()) > 0:
             if fpr.binary_reject:
-                rej = ".changes file contains files of architectures not permitted for fingerprint %s" % fpr.fingerprint
-                rej += "\narchitectures involved are: ", ",".join(tmparches.keys())
+                rej = "changes file contains files of architectures not permitted for fingerprint %s" % fpr.fingerprint
+                if len(tmparches.keys()) == 1:
+                    rej += "\n\narchitecture involved is: %s" % ",".join(tmparches.keys())
+                else:
+                    rej += "\n\narchitectures involved are: %s" % ",".join(tmparches.keys())
                 self.rejects.append(rej)
             else:
                 # TODO: This is where we'll implement reject vs throw away binaries later
@@ -1536,22 +1797,13 @@ class Upload(object):
         if rej:
             return
 
-        ## the most recent version of the package uploaded to unstable or
-        ## experimental includes the field "DM-Upload-Allowed: yes" in the source
-        ## section of its control file
-        q = session.query(DBSource).filter_by(source=self.pkg.changes["source"])
-        q = q.join(SrcAssociation)
-        q = q.join(Suite).filter(Suite.suite_name.in_(['unstable', 'experimental']))
-        q = q.order_by(desc('source.version')).limit(1)
+        r = get_newest_source(self.pkg.changes["source"], session)
 
-        r = q.all()
-
-        if len(r) != 1:
-            rej = "Could not find existing source package %s in unstable or experimental and this is a DM upload" % self.pkg.changes["source"]
+        if r is None:
+            rej = "Could not find existing source package %s in the DM allowed suites and this is a DM upload" % self.pkg.changes["source"]
             self.rejects.append(rej)
             return
 
-        r = r[0]
         if not r.dm_upload_allowed:
             rej = "Source package %s does not have 'DM-Upload-Allowed: yes' in its most recent version (%s)" % (self.pkg.changes["source"], r.version)
             self.rejects.append(rej)
@@ -1567,10 +1819,10 @@ class Upload(object):
         ## experimental lists the uploader in the Maintainer: or Uploaders: fields (ie,
         ## non-developer maintainers cannot NMU or hijack packages)
 
-        # srcuploaders includes the maintainer
+        # uploader includes the maintainer
         accept = False
-        for sup in r.srcuploaders:
-            (rfc822, rfc2047, name, email) = sup.maintainer.get_split_maintainer()
+        for uploader in r.uploaders:
+            (rfc822, rfc2047, name, email) = uploader.get_split_maintainer()
             # Eww - I hope we never have two people with the same name in Debian
             if email == fpr.uid.uid or name == fpr.uid.name:
                 accept = True
@@ -1583,11 +1835,7 @@ class Upload(object):
         ## none of the packages are being taken over from other source packages
         for b in self.pkg.changes["binary"].keys():
             for suite in self.pkg.changes["distribution"].keys():
-                q = session.query(DBSource)
-                q = q.join(DBBinary).filter_by(package=b)
-                q = q.join(BinAssociation).join(Suite).filter_by(suite_name=suite)
-
-                for s in q.all():
+                for s in get_source_by_package_and_suite(b, suite, session):
                     if s.source != self.pkg.changes["source"]:
                         self.rejects.append("%s may not hijack %s from source package %s in suite %s" % (fpr.uid.uid, b, s, suite))
 
@@ -1607,7 +1855,7 @@ class Upload(object):
 
         # Also only check if there is a file defined (and existant) with
         # checks.
-        transpath = cnf.get("Dinstall::Reject::ReleaseTransitions", "")
+        transpath = cnf.get("Dinstall::ReleaseTransitions", "")
         if transpath == "" or not os.path.exists(transpath):
             return
 
@@ -1616,7 +1864,7 @@ class Upload(object):
         sourcecontent = sourcefile.read()
         try:
             transitions = yaml.load(sourcecontent)
-        except yaml.YAMLError, msg:
+        except yaml.YAMLError as msg:
             # This shouldn't happen, there is a wrapper to edit the file which
             # checks it, but we prefer to be safe than ending up rejecting
             # everything.
@@ -1632,7 +1880,7 @@ class Upload(object):
             # Will be None if nothing is in testing.
             current = get_source_in_suite(source, "testing", session)
             if current is not None:
-                compare = apt_pkg.VersionCompare(current.version, expected)
+                compare = apt_pkg.version_compare(current.version, expected)
 
             if current is None or compare < 0:
                 # This is still valid, the current version in testing is older than
@@ -1678,8 +1926,7 @@ transition is done."""
         # This is for direport's benefit...
         f = re_fdnic.sub("\n .\n", self.pkg.changes.get("changes", ""))
 
-        if byhand or new:
-            summary += "Changes: " + f
+        summary += "\n\nChanges:\n" + f
 
         summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"
 
@@ -1760,24 +2007,31 @@ distribution."""
         """
 
         cnf = Config()
-        announcetemplate = os.path.join(cnf["Dir::Templates"], 'process-unchecked.announce')
+
+        # Skip all of this if not sending mail to avoid confusing people
+        if cnf.has_key("Dinstall::Options::No-Mail") and cnf["Dinstall::Options::No-Mail"]:
+            return ""
 
         # Only do announcements for source uploads with a recent dpkg-dev installed
         if float(self.pkg.changes.get("format", 0)) < 1.6 or not \
            self.pkg.changes["architecture"].has_key("source"):
             return ""
 
-        lists_done = {}
-        summary = ""
+        announcetemplate = os.path.join(cnf["Dir::Templates"], 'process-unchecked.announce')
 
-        self.Subst["__SHORT_SUMMARY__"] = short_summary
+        lists_todo = {}
+        summary = ""
 
+        # Get a unique list of target lists
         for dist in self.pkg.changes["distribution"].keys():
-            announce_list = cnf.Find("Suite::%s::Announce" % (dist))
-            if announce_list == "" or lists_done.has_key(announce_list):
-                continue
+            suite = get_suite(dist)
+            if suite is None: continue
+            for tgt in suite.announce:
+                lists_todo[tgt] = 1
 
-            lists_done[announce_list] = 1
+        self.Subst["__SHORT_SUMMARY__"] = short_summary
+
+        for announce_list in lists_todo.keys():
             summary += "Announcing to %s\n" % (announce_list)
 
             if action:
@@ -1793,7 +2047,7 @@ distribution."""
 
                 del self.Subst["__ANNOUNCE_LIST_ADDRESS__"]
 
-        if cnf.FindB("Dinstall::CloseBugs"):
+        if cnf.find_b("Dinstall::CloseBugs") and cnf.has_key("Dinstall::BugServer"):
             summary = self.close_bugs(summary, action)
 
         del self.Subst["__SHORT_SUMMARY__"]
@@ -1825,6 +2079,7 @@ distribution."""
         print "Installing."
         self.logger.log(["installing changes", self.pkg.changes_file])
 
+        binaries = []
         poolfiles = []
 
         # Add the .dsc file to the DB first
@@ -1837,7 +2092,9 @@ distribution."""
         # Add .deb / .udeb files to the DB (type is always deb, dbtype is udeb/deb)
         for newfile, entry in self.pkg.files.items():
             if entry["type"] == "deb":
-                poolfiles.append(add_deb_to_db(self, newfile, session))
+                b, pf = add_deb_to_db(self, newfile, session)
+                binaries.append(b)
+                poolfiles.append(pf)
 
         # If this is a sourceful diff only upload that is moving
         # cross-component we need to copy the .orig files into the new
@@ -1910,10 +2167,9 @@ distribution."""
             stats.accept_bytes += float(entry["size"])
 
         # Copy the .changes file across for suite which need it.
-        copy_changes = {}
-        for suite_name in self.pkg.changes["distribution"].keys():
-            if cnf.has_key("Suite::%s::CopyChanges" % (suite_name)):
-                copy_changes[cnf["Suite::%s::CopyChanges" % (suite_name)]] = ""
+        copy_changes = dict([(x.copychanges, '')
+                             for x in session.query(Suite).filter(Suite.suite_name.in_(self.pkg.changes["distribution"].keys())).all()
+                             if x.copychanges is not None])
 
         for dest in copy_changes.keys():
             utils.copy(self.pkg.changes_file, os.path.join(cnf["Dir::Root"], dest))
@@ -1923,15 +2179,31 @@ distribution."""
         # Our SQL session will automatically start a new transaction after
         # the last commit
 
+        # Now ensure that the metadata has been added
+        # This has to be done after we copy the files into the pool
+        # For source if we have it:
+        if self.pkg.changes["architecture"].has_key("source"):
+            import_metadata_into_db(source, session)
+
+        # Now for any of our binaries
+        for b in binaries:
+            import_metadata_into_db(b, session)
+
+        session.commit()
+
         # Move the .changes into the 'done' directory
+        ye, mo, da = time.gmtime()[0:3]
+        donedir = os.path.join(cnf["Dir::Done"], str(ye), "%0.2d" % mo, "%0.2d" % da)
+        if not os.path.isdir(donedir):
+            os.makedirs(donedir)
+
         utils.move(self.pkg.changes_file,
-                   os.path.join(cnf["Dir::Queue::Done"], os.path.basename(self.pkg.changes_file)))
+                   os.path.join(donedir, os.path.basename(self.pkg.changes_file)))
 
-        if self.pkg.changes["architecture"].has_key("source") and cnf.get("Dir::UrgencyLog"):
+        if self.pkg.changes["architecture"].has_key("source"):
             UrgencyLog().log(self.pkg.dsc["source"], self.pkg.dsc["version"], self.pkg.changes["urgency"])
 
         self.update_subst()
-        self.Subst["__SUITE__"] = ""
         self.Subst["__SUMMARY__"] = summary
         mail_message = utils.TemplateSubst(self.Subst,
                                            os.path.join(cnf["Dir::Templates"], 'process-unchecked.accepted'))
@@ -1939,19 +2211,19 @@ distribution."""
         self.announce(short_summary, 1)
 
         ## Helper stuff for DebBugs Version Tracking
-        if cnf.Find("Dir::Queue::BTSVersionTrack"):
+        if cnf.find("Dir::BTSVersionTrack"):
             if self.pkg.changes["architecture"].has_key("source"):
-                (fd, temp_filename) = utils.temp_filename(cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
+                (fd, temp_filename) = utils.temp_filename(cnf["Dir::BTSVersionTrack"], prefix=".")
                 version_history = os.fdopen(fd, 'w')
                 version_history.write(self.pkg.dsc["bts changelog"])
                 version_history.close()
-                filename = "%s/%s" % (cnf["Dir::Queue::BTSVersionTrack"],
+                filename = "%s/%s" % (cnf["Dir::BTSVersionTrack"],
                                       self.pkg.changes_file[:-8]+".versions")
                 os.rename(temp_filename, filename)
-                os.chmod(filename, 0644)
+                os.chmod(filename, 0o644)
 
             # Write out the binary -> source mapping.
-            (fd, temp_filename) = utils.temp_filename(cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
+            (fd, temp_filename) = utils.temp_filename(cnf["Dir::BTSVersionTrack"], prefix=".")
             debinfo = os.fdopen(fd, 'w')
             for name, entry in sorted(self.pkg.files.items()):
                 if entry["type"] == "deb":
@@ -1960,10 +2232,10 @@ distribution."""
                                      entry["source version"]])
                     debinfo.write(line+"\n")
             debinfo.close()
-            filename = "%s/%s" % (cnf["Dir::Queue::BTSVersionTrack"],
+            filename = "%s/%s" % (cnf["Dir::BTSVersionTrack"],
                                   self.pkg.changes_file[:-8]+".debinfo")
             os.rename(temp_filename, filename)
-            os.chmod(filename, 0644)
+            os.chmod(filename, 0o644)
 
         session.commit()
 
@@ -1992,7 +2264,7 @@ distribution."""
         cnf = Config()
 
         # Abandon the check if override disparity checks have been disabled
-        if not cnf.FindB("Dinstall::OverrideDisparityCheck"):
+        if not cnf.find_b("Dinstall::OverrideDisparityCheck"):
             return
 
         summary = self.pkg.check_override()
@@ -2062,11 +2334,11 @@ distribution."""
             if os.access(file_entry, os.R_OK) == 0:
                 continue
 
-            dest_file = os.path.join(cnf["Dir::Queue::Reject"], file_entry)
+            dest_file = os.path.join(cnf["Dir::Reject"], file_entry)
 
             try:
-                dest_fd = os.open(dest_file, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0644)
-            except OSError, e:
+                dest_fd = os.open(dest_file, os.O_RDWR | os.O_CREAT | os.O_EXCL, 0o644)
+            except OSError as e:
                 # File exists?  Let's find a new name by adding a number
                 if e.errno == errno.EEXIST:
                     try:
@@ -2074,13 +2346,13 @@ distribution."""
                     except NoFreeFilenameError:
                         # Something's either gone badly Pete Tong, or
                         # someone is trying to exploit us.
-                        utils.warn("**WARNING** failed to find a free filename for %s in %s." % (file_entry, cnf["Dir::Queue::Reject"]))
+                        utils.warn("**WARNING** failed to find a free filename for %s in %s." % (file_entry, cnf["Dir::Reject"]))
                         return
 
                     # Make sure we really got it
                     try:
-                        dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
-                    except OSError, e:
+                        dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0o644)
+                    except OSError as e:
                         # Likewise
                         utils.warn("**WARNING** failed to claim %s in the reject directory." % (file_entry))
                         return
@@ -2088,7 +2360,7 @@ distribution."""
                     raise
             # If we got here, we own the destination file, so we can
             # safely overwrite it.
-            utils.move(file_entry, dest_file, 1, perms=0660)
+            utils.move(file_entry, dest_file, 1, perms=0o660)
             os.close(dest_fd)
 
     ###########################################################################
@@ -2144,17 +2416,26 @@ distribution."""
         cnf = Config()
 
         reason_filename = self.pkg.changes_file[:-8] + ".reason"
-        reason_filename = os.path.join(cnf["Dir::Queue::Reject"], reason_filename)
+        reason_filename = os.path.join(cnf["Dir::Reject"], reason_filename)
+        changesfile = os.path.join(cnf["Dir::Reject"], self.pkg.changes_file)
 
         # Move all the files into the reject directory
         reject_files = self.pkg.files.keys() + [self.pkg.changes_file]
         self.force_reject(reject_files)
 
+        # Change permissions of the .changes file to be world readable
+        try:
+            os.chmod(changesfile, os.stat(changesfile).st_mode | stat.S_IROTH)
+        except OSError as (errno, strerror):
+            # Ignore 'Operation not permitted' error.
+            if errno != 1:
+                raise
+
         # If we fail here someone is probably trying to exploit the race
         # so let's just raise an exception ...
         if os.path.exists(reason_filename):
             os.unlink(reason_filename)
-        reason_fd = os.open(reason_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
+        reason_fd = os.open(reason_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0o644)
 
         rej_template = os.path.join(cnf["Dir::Templates"], "queue.rejected")
 
@@ -2188,6 +2469,8 @@ distribution."""
         if self.logger:
             self.logger.log(["rejected", self.pkg.changes_file])
 
+        stats = SummaryStats()
+        stats.reject_count += 1
         return 0
 
     ################################################################################
@@ -2222,8 +2505,9 @@ distribution."""
             file_type = binary_type
 
         # Override suite name; used for example with proposed-updates
-        if cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
-            suite = cnf["Suite::%s::OverrideSuite" % (suite)]
+        oldsuite = get_suite(suite, session)
+        if (not oldsuite is None) and oldsuite.overridesuite:
+            suite = oldsuite.overridesuite
 
         result = get_override(package, suite, component, file_type, session)
 
@@ -2253,10 +2537,10 @@ distribution."""
         """
         Cnf = Config()
         anyversion = None
-        anysuite = [suite] + Cnf.ValueList("Suite::%s::VersionChecks::Enhances" % (suite))
+        anysuite = [suite] + [ vc.reference.suite_name for vc in get_version_checks(suite, "Enhances") ]
         for (s, v) in sv_list:
             if s in [ x.lower() for x in anysuite ]:
-                if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
+                if not anyversion or apt_pkg.version_compare(anyversion, v) <= 0:
                     anyversion = v
 
         return anyversion
@@ -2283,15 +2567,21 @@ distribution."""
 
         # Check versions for each target suite
         for target_suite in self.pkg.changes["distribution"].keys():
-            must_be_newer_than = [ i.lower() for i in cnf.ValueList("Suite::%s::VersionChecks::MustBeNewerThan" % (target_suite)) ]
-            must_be_older_than = [ i.lower() for i in cnf.ValueList("Suite::%s::VersionChecks::MustBeOlderThan" % (target_suite)) ]
+            # Check we can find the target suite
+            ts = get_suite(target_suite)
+            if ts is None:
+                self.rejects.append("Cannot find target suite %s to perform version checks" % target_suite)
+                continue
+
+            must_be_newer_than = [ vc.reference.suite_name for vc in get_version_checks(target_suite, "MustBeNewerThan") ]
+            must_be_older_than = [ vc.reference.suite_name for vc in get_version_checks(target_suite, "MustBeOlderThan") ]
 
             # Enforce "must be newer than target suite" even if conffile omits it
             if target_suite not in must_be_newer_than:
                 must_be_newer_than.append(target_suite)
 
             for (suite, existent_version) in sv_list:
-                vercmp = apt_pkg.VersionCompare(new_version, existent_version)
+                vercmp = apt_pkg.version_compare(new_version, existent_version)
 
                 if suite in must_be_newer_than and sourceful and vercmp < 1:
                     self.rejects.append("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (filename, existent_version, suite, new_version, target_suite))
@@ -2325,12 +2615,12 @@ distribution."""
                             # we could just stick with the "...old version..." REJECT
                             # for this, I think.
                             self.rejects.append("Won't propogate NEW packages.")
-                        elif apt_pkg.VersionCompare(new_version, add_version) < 0:
+                        elif apt_pkg.version_compare(new_version, add_version) < 0:
                             # propogation would be redundant. no need to reject though.
                             self.warnings.append("ignoring versionconflict: %s: old version (%s) in %s <= new version (%s) targeted at %s." % (filename, existent_version, suite, new_version, target_suite))
                             cansave = 1
-                        elif apt_pkg.VersionCompare(new_version, add_version) > 0 and \
-                             apt_pkg.VersionCompare(add_version, target_version) >= 0:
+                        elif apt_pkg.version_compare(new_version, add_version) > 0 and \
+                             apt_pkg.version_compare(add_version, target_version) >= 0:
                             # propogate!!
                             self.warnings.append("Propogating upload to %s" % (addsuite))
                             self.pkg.changes.setdefault("propdistribution", {})
@@ -2343,12 +2633,10 @@ distribution."""
     ################################################################################
     def check_binary_against_db(self, filename, session):
         # Ensure version is sane
-        q = session.query(BinAssociation)
-        q = q.join(DBBinary).filter(DBBinary.package==self.pkg.files[filename]["package"])
-        q = q.join(Architecture).filter(Architecture.arch_string.in_([self.pkg.files[filename]["architecture"], 'all']))
-
-        self.cross_suite_version_check([ (x.suite.suite_name, x.binary.version) for x in q.all() ],
-                                       filename, self.pkg.files[filename]["version"], sourceful=False)
+        self.cross_suite_version_check( \
+            get_suite_version_by_package(self.pkg.files[filename]["package"], \
+                self.pkg.files[filename]["architecture"], session),
+            filename, self.pkg.files[filename]["version"], sourceful=False)
 
         # Check for any existing copies of the file
         q = session.query(DBBinary).filter_by(package=self.pkg.files[filename]["package"])
@@ -2365,11 +2653,9 @@ distribution."""
         version = self.pkg.dsc.get("version")
 
         # Ensure version is sane
-        q = session.query(SrcAssociation)
-        q = q.join(DBSource).filter(DBSource.source==source)
-
-        self.cross_suite_version_check([ (x.suite.suite_name, x.source.version) for x in q.all() ],
-                                       filename, version, sourceful=True)
+        self.cross_suite_version_check( \
+            get_suite_version_by_source(source, session), filename, version,
+            sourceful=True)
 
     ################################################################################
     def check_dsc_against_db(self, filename, session):
@@ -2489,12 +2775,15 @@ distribution."""
                     orig_files[dsc_name]["path"] = old_file
                     orig_files[dsc_name]["location"] = x.location.location_id
                 else:
-                    # TODO: Record the queues and info in the DB so we don't hardcode all this crap
+                    # TODO: Determine queue list dynamically
                     # Not there? Check the queue directories...
-                    for directory in [ "New", "Byhand", "ProposedUpdates", "OldProposedUpdates", "Embargoed", "Unembargoed" ]:
-                        if not Cnf.has_key("Dir::Queue::%s" % (directory)):
+                    for queue_name in [ "byhand", "new", "proposedupdates", "oldproposedupdates", "embargoed", "unembargoed" ]:
+                        queue = get_policy_queue(queue_name, session)
+                        if not queue:
                             continue
-                        in_otherdir = os.path.join(Cnf["Dir::Queue::%s" % (directory)], dsc_name)
+
+                        in_otherdir = os.path.join(queue.path, dsc_name)
+
                         if os.path.exists(in_otherdir):
                             in_otherdir_fh = utils.open_file(in_otherdir)
                             actual_md5 = apt_pkg.md5sum(in_otherdir_fh)
@@ -2536,14 +2825,15 @@ distribution."""
                 source_version = entry["source version"]
                 source_package = entry["source package"]
                 if not self.pkg.changes["architecture"].has_key("source") \
-                   and not source_exists(source_package, source_version, self.pkg.changes["distribution"].keys(), session):
+                   and not source_exists(source_package, source_version, \
+                    suites = self.pkg.changes["distribution"].keys(), session = session):
                     source_epochless_version = re_no_epoch.sub('', source_version)
                     dsc_filename = "%s_%s.dsc" % (source_package, source_epochless_version)
                     found = False
-                    for q in ["Embargoed", "Unembargoed", "Newstage"]:
-                        if cnf.has_key("Dir::Queue::%s" % (q)):
-                            if os.path.exists(cnf["Dir::Queue::%s" % (q)] + '/' + dsc_filename):
-                                found = True
+                    for queue_name in ["embargoed", "unembargoed", "newstage"]:
+                        queue = get_policy_queue(queue_name, session)
+                        if queue and os.path.exists(os.path.join(queue.path, dsc_filename)):
+                            found = True
                     if not found:
                         self.rejects.append("no source found for %s %s (%s)." % (source_package, source_version, f))
 
@@ -2583,7 +2873,9 @@ distribution."""
                 source_version = entry["source version"]
                 source_package = entry["source package"]
                 if not self.pkg.changes["architecture"].has_key("source") \
-                   and not source_exists(source_package, source_version,  self.pkg.changes["distribution"].keys()):
+                   and not source_exists(source_package, source_version, \
+                    suites = self.pkg.changes["distribution"].keys(), \
+                    session = session):
                     self.rejects.append("no source found for %s %s (%s)." % (source_package, source_version, checkfile))
 
             # Version and file overwrite checks
@@ -2612,45 +2904,6 @@ distribution."""
                 if not self.in_override_p(entry["package"], entry["component"], suite, entry.get("dbtype",""), checkfile, session):
                     self.rejects.append("%s is NEW for %s." % (checkfile, suite))
 
-    ################################################################################
-    # This is not really a reject, but an unaccept, but since a) the code for
-    # that is non-trivial (reopen bugs, unannounce etc.), b) this should be
-    # extremely rare, for now we'll go with whining at our admin folks...
-
-    def do_unaccept(self):
-        cnf = Config()
-
-        self.update_subst()
-        self.Subst["__REJECTOR_ADDRESS__"] = cnf["Dinstall::MyEmailAddress"]
-        self.Subst["__REJECT_MESSAGE__"] = self.package_info()
-        self.Subst["__CC__"] = "Cc: " + cnf["Dinstall::MyEmailAddress"]
-        self.Subst["__BCC__"] = "X-DAK: dak process-accepted"
-        if cnf.has_key("Dinstall::Bcc"):
-            self.Subst["__BCC__"] += "\nBcc: %s" % (cnf["Dinstall::Bcc"])
-
-        template = os.path.join(cnf["Dir::Templates"], "process-accepted.unaccept")
-
-        reject_mail_message = utils.TemplateSubst(self.Subst, template)
-
-        # Write the rejection email out as the <foo>.reason file
-        reason_filename = os.path.basename(self.pkg.changes_file[:-8]) + ".reason"
-        reject_filename = os.path.join(cnf["Dir::Queue::Reject"], reason_filename)
-
-        # If we fail here someone is probably trying to exploit the race
-        # so let's just raise an exception ...
-        if os.path.exists(reject_filename):
-            os.unlink(reject_filename)
-
-        fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
-        os.write(fd, reject_mail_message)
-        os.close(fd)
-
-        utils.send_mail(reject_mail_message)
-
-        del self.Subst["__REJECTOR_ADDRESS__"]
-        del self.Subst["__REJECT_MESSAGE__"]
-        del self.Subst["__CC__"]
-
     ################################################################################
     # If any file of an upload has a recent mtime then chances are good
     # the file is still being uploaded.