]> git.decadent.org.uk Git - dak.git/blob - daklib/dbconn.py
First implementation of make-changelog command
[dak.git] / daklib / dbconn.py
1 #!/usr/bin/python
2
3 """ DB access class
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2000, 2001, 2002, 2003, 2004, 2006  James Troup <james@nocrew.org>
7 @copyright: 2008-2009  Mark Hymers <mhy@debian.org>
8 @copyright: 2009, 2010  Joerg Jaspert <joerg@debian.org>
9 @copyright: 2009  Mike O'Connor <stew@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ################################################################################
28
29 # < mhy> I need a funny comment
30 # < sgran> two peanuts were walking down a dark street
31 # < sgran> one was a-salted
32 #  * mhy looks up the definition of "funny"
33
34 ################################################################################
35
36 import os
37 import re
38 import psycopg2
39 import traceback
40 from datetime import datetime, timedelta
41 from errno import ENOENT
42 from tempfile import mkstemp, mkdtemp
43
44 from inspect import getargspec
45
46 import sqlalchemy
47 from sqlalchemy import create_engine, Table, MetaData
48 from sqlalchemy.orm import sessionmaker, mapper, relation
49 from sqlalchemy import types as sqltypes
50
51 # Don't remove this, we re-export the exceptions to scripts which import us
52 from sqlalchemy.exc import *
53 from sqlalchemy.orm.exc import NoResultFound
54
55 # Only import Config until Queue stuff is changed to store its config
56 # in the database
57 from config import Config
58 from textutils import fix_maintainer
59
60 ################################################################################
61
62 # Patch in support for the debversion field type so that it works during
63 # reflection
64
65 class DebVersion(sqltypes.Text):
66     """
67     Support the debversion type
68     """
69
70     def get_col_spec(self):
71         return "DEBVERSION"
72
73 sa_major_version = sqlalchemy.__version__[0:3]
74 if sa_major_version == "0.5":
75     from sqlalchemy.databases import postgres
76     postgres.ischema_names['debversion'] = DebVersion
77 else:
78     raise Exception("dak isn't ported to SQLA versions != 0.5 yet.  See daklib/dbconn.py")
79
80 ################################################################################
81
82 __all__ = ['IntegrityError', 'SQLAlchemyError']
83
84 ################################################################################
85
86 def session_wrapper(fn):
87     """
88     Wrapper around common ".., session=None):" handling. If the wrapped
89     function is called without passing 'session', we create a local one
90     and destroy it when the function ends.
91
92     Also attaches a commit_or_flush method to the session; if we created a
93     local session, this is a synonym for session.commit(), otherwise it is a
94     synonym for session.flush().
95     """
96
97     def wrapped(*args, **kwargs):
98         private_transaction = False
99
100         # Find the session object
101         session = kwargs.get('session')
102
103         if session is None:
104             if len(args) <= len(getargspec(fn)[0]) - 1:
105                 # No session specified as last argument or in kwargs
106                 private_transaction = True
107                 session = kwargs['session'] = DBConn().session()
108             else:
109                 # Session is last argument in args
110                 session = args[-1]
111                 if session is None:
112                     args = list(args)
113                     session = args[-1] = DBConn().session()
114                     private_transaction = True
115
116         if private_transaction:
117             session.commit_or_flush = session.commit
118         else:
119             session.commit_or_flush = session.flush
120
121         try:
122             return fn(*args, **kwargs)
123         finally:
124             if private_transaction:
125                 # We created a session; close it.
126                 session.close()
127
128     wrapped.__doc__ = fn.__doc__
129     wrapped.func_name = fn.func_name
130
131     return wrapped
132
133 __all__.append('session_wrapper')
134
135 ################################################################################
136
137 class Architecture(object):
138     def __init__(self, *args, **kwargs):
139         pass
140
141     def __eq__(self, val):
142         if isinstance(val, str):
143             return (self.arch_string== val)
144         # This signals to use the normal comparison operator
145         return NotImplemented
146
147     def __ne__(self, val):
148         if isinstance(val, str):
149             return (self.arch_string != val)
150         # This signals to use the normal comparison operator
151         return NotImplemented
152
153     def __repr__(self):
154         return '<Architecture %s>' % self.arch_string
155
156 __all__.append('Architecture')
157
158 @session_wrapper
159 def get_architecture(architecture, session=None):
160     """
161     Returns database id for given C{architecture}.
162
163     @type architecture: string
164     @param architecture: The name of the architecture
165
166     @type session: Session
167     @param session: Optional SQLA session object (a temporary one will be
168     generated if not supplied)
169
170     @rtype: Architecture
171     @return: Architecture object for the given arch (None if not present)
172     """
173
174     q = session.query(Architecture).filter_by(arch_string=architecture)
175
176     try:
177         return q.one()
178     except NoResultFound:
179         return None
180
181 __all__.append('get_architecture')
182
183 @session_wrapper
184 def get_architecture_suites(architecture, session=None):
185     """
186     Returns list of Suite objects for given C{architecture} name
187
188     @type architecture: str
189     @param architecture: Architecture name to search for
190
191     @type session: Session
192     @param session: Optional SQL session object (a temporary one will be
193     generated if not supplied)
194
195     @rtype: list
196     @return: list of Suite objects for the given name (may be empty)
197     """
198
199     q = session.query(Suite)
200     q = q.join(SuiteArchitecture)
201     q = q.join(Architecture).filter_by(arch_string=architecture).order_by('suite_name')
202
203     ret = q.all()
204
205     return ret
206
207 __all__.append('get_architecture_suites')
208
209 ################################################################################
210
211 class Archive(object):
212     def __init__(self, *args, **kwargs):
213         pass
214
215     def __repr__(self):
216         return '<Archive %s>' % self.archive_name
217
218 __all__.append('Archive')
219
220 @session_wrapper
221 def get_archive(archive, session=None):
222     """
223     returns database id for given C{archive}.
224
225     @type archive: string
226     @param archive: the name of the arhive
227
228     @type session: Session
229     @param session: Optional SQLA session object (a temporary one will be
230     generated if not supplied)
231
232     @rtype: Archive
233     @return: Archive object for the given name (None if not present)
234
235     """
236     archive = archive.lower()
237
238     q = session.query(Archive).filter_by(archive_name=archive)
239
240     try:
241         return q.one()
242     except NoResultFound:
243         return None
244
245 __all__.append('get_archive')
246
247 ################################################################################
248
249 class BinAssociation(object):
250     def __init__(self, *args, **kwargs):
251         pass
252
253     def __repr__(self):
254         return '<BinAssociation %s (%s, %s)>' % (self.ba_id, self.binary, self.suite)
255
256 __all__.append('BinAssociation')
257
258 ################################################################################
259
260 class BinContents(object):
261     def __init__(self, *args, **kwargs):
262         pass
263
264     def __repr__(self):
265         return '<BinContents (%s, %s)>' % (self.binary, self.filename)
266
267 __all__.append('BinContents')
268
269 ################################################################################
270
271 class DBBinary(object):
272     def __init__(self, *args, **kwargs):
273         pass
274
275     def __repr__(self):
276         return '<DBBinary %s (%s, %s)>' % (self.package, self.version, self.architecture)
277
278 __all__.append('DBBinary')
279
280 @session_wrapper
281 def get_suites_binary_in(package, session=None):
282     """
283     Returns list of Suite objects which given C{package} name is in
284
285     @type package: str
286     @param package: DBBinary package name to search for
287
288     @rtype: list
289     @return: list of Suite objects for the given package
290     """
291
292     return session.query(Suite).join(BinAssociation).join(DBBinary).filter_by(package=package).all()
293
294 __all__.append('get_suites_binary_in')
295
296 @session_wrapper
297 def get_binary_from_id(binary_id, session=None):
298     """
299     Returns DBBinary object for given C{id}
300
301     @type binary_id: int
302     @param binary_id: Id of the required binary
303
304     @type session: Session
305     @param session: Optional SQLA session object (a temporary one will be
306     generated if not supplied)
307
308     @rtype: DBBinary
309     @return: DBBinary object for the given binary (None if not present)
310     """
311
312     q = session.query(DBBinary).filter_by(binary_id=binary_id)
313
314     try:
315         return q.one()
316     except NoResultFound:
317         return None
318
319 __all__.append('get_binary_from_id')
320
321 @session_wrapper
322 def get_binaries_from_name(package, version=None, architecture=None, session=None):
323     """
324     Returns list of DBBinary objects for given C{package} name
325
326     @type package: str
327     @param package: DBBinary package name to search for
328
329     @type version: str or None
330     @param version: Version to search for (or None)
331
332     @type architecture: str, list or None
333     @param architecture: Architectures to limit to (or None if no limit)
334
335     @type session: Session
336     @param session: Optional SQL session object (a temporary one will be
337     generated if not supplied)
338
339     @rtype: list
340     @return: list of DBBinary objects for the given name (may be empty)
341     """
342
343     q = session.query(DBBinary).filter_by(package=package)
344
345     if version is not None:
346         q = q.filter_by(version=version)
347
348     if architecture is not None:
349         if not isinstance(architecture, list):
350             architecture = [architecture]
351         q = q.join(Architecture).filter(Architecture.arch_string.in_(architecture))
352
353     ret = q.all()
354
355     return ret
356
357 __all__.append('get_binaries_from_name')
358
359 @session_wrapper
360 def get_binaries_from_source_id(source_id, session=None):
361     """
362     Returns list of DBBinary objects for given C{source_id}
363
364     @type source_id: int
365     @param source_id: source_id to search for
366
367     @type session: Session
368     @param session: Optional SQL session object (a temporary one will be
369     generated if not supplied)
370
371     @rtype: list
372     @return: list of DBBinary objects for the given name (may be empty)
373     """
374
375     return session.query(DBBinary).filter_by(source_id=source_id).all()
376
377 __all__.append('get_binaries_from_source_id')
378
379 @session_wrapper
380 def get_binary_from_name_suite(package, suitename, session=None):
381     ### For dak examine-package
382     ### XXX: Doesn't use object API yet
383
384     sql = """SELECT DISTINCT(b.package), b.version, c.name, su.suite_name
385              FROM binaries b, files fi, location l, component c, bin_associations ba, suite su
386              WHERE b.package='%(package)s'
387                AND b.file = fi.id
388                AND fi.location = l.id
389                AND l.component = c.id
390                AND ba.bin=b.id
391                AND ba.suite = su.id
392                AND su.suite_name %(suitename)s
393           ORDER BY b.version DESC"""
394
395     return session.execute(sql % {'package': package, 'suitename': suitename})
396
397 __all__.append('get_binary_from_name_suite')
398
399 @session_wrapper
400 def get_binary_components(package, suitename, arch, session=None):
401     # Check for packages that have moved from one component to another
402     query = """SELECT c.name FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f
403     WHERE b.package=:package AND s.suite_name=:suitename
404       AND (a.arch_string = :arch OR a.arch_string = 'all')
405       AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id
406       AND f.location = l.id
407       AND l.component = c.id
408       AND b.file = f.id"""
409
410     vals = {'package': package, 'suitename': suitename, 'arch': arch}
411
412     return session.execute(query, vals)
413
414 __all__.append('get_binary_components')
415
416 ################################################################################
417
418 class BinaryACL(object):
419     def __init__(self, *args, **kwargs):
420         pass
421
422     def __repr__(self):
423         return '<BinaryACL %s>' % self.binary_acl_id
424
425 __all__.append('BinaryACL')
426
427 ################################################################################
428
429 class BinaryACLMap(object):
430     def __init__(self, *args, **kwargs):
431         pass
432
433     def __repr__(self):
434         return '<BinaryACLMap %s>' % self.binary_acl_map_id
435
436 __all__.append('BinaryACLMap')
437
438 ################################################################################
439
440 MINIMAL_APT_CONF="""
441 Dir
442 {
443    ArchiveDir "%(archivepath)s";
444    OverrideDir "/srv/ftp.debian.org/scripts/override/";
445    CacheDir "/srv/ftp.debian.org/database/";
446 };
447
448 Default
449 {
450    Packages::Compress ". bzip2 gzip";
451    Sources::Compress ". bzip2 gzip";
452    DeLinkLimit 0;
453    FileMode 0664;
454 }
455
456 bindirectory "incoming"
457 {
458    Packages "Packages";
459    Contents " ";
460
461    BinOverride "override.sid.all3";
462    BinCacheDB "packages-accepted.db";
463
464    FileList "%(filelist)s";
465
466    PathPrefix "";
467    Packages::Extensions ".deb .udeb";
468 };
469
470 bindirectory "incoming/"
471 {
472    Sources "Sources";
473    BinOverride "override.sid.all3";
474    SrcOverride "override.sid.all3.src";
475    FileList "%(filelist)s";
476 };
477 """
478
479 class BuildQueue(object):
480     def __init__(self, *args, **kwargs):
481         pass
482
483     def __repr__(self):
484         return '<BuildQueue %s>' % self.queue_name
485
486     def write_metadata(self, starttime, force=False):
487         # Do we write out metafiles?
488         if not (force or self.generate_metadata):
489             return
490
491         session = DBConn().session().object_session(self)
492
493         fl_fd = fl_name = ac_fd = ac_name = None
494         tempdir = None
495         arches = " ".join([ a.arch_string for a in session.query(Architecture).all() if a.arch_string != 'source' ])
496         startdir = os.getcwd()
497
498         try:
499             # Grab files we want to include
500             newer = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) > starttime).all()
501             # Write file list with newer files
502             (fl_fd, fl_name) = mkstemp()
503             for n in newer:
504                 os.write(fl_fd, '%s\n' % n.fullpath)
505             os.close(fl_fd)
506
507             # Write minimal apt.conf
508             # TODO: Remove hardcoding from template
509             (ac_fd, ac_name) = mkstemp()
510             os.write(ac_fd, MINIMAL_APT_CONF % {'archivepath': self.path,
511                                                 'filelist': fl_name})
512             os.close(ac_fd)
513
514             # Run apt-ftparchive generate
515             os.chdir(os.path.dirname(ac_name))
516             os.system('apt-ftparchive -qq -o APT::FTPArchive::Contents=off generate %s' % os.path.basename(ac_name))
517
518             # Run apt-ftparchive release
519             # TODO: Eww - fix this
520             bname = os.path.basename(self.path)
521             os.chdir(self.path)
522             os.chdir('..')
523
524             # We have to remove the Release file otherwise it'll be included in the
525             # new one
526             try:
527                 os.unlink(os.path.join(bname, 'Release'))
528             except OSError:
529                 pass
530
531             os.system("""apt-ftparchive -qq -o APT::FTPArchive::Release::Origin="%s" -o APT::FTPArchive::Release::Label="%s" -o APT::FTPArchive::Release::Description="%s" -o APT::FTPArchive::Release::Architectures="%s" release %s > Release""" % (self.origin, self.label, self.releasedescription, arches, bname))
532
533             # Crude hack with open and append, but this whole section is and should be redone.
534             if self.notautomatic:
535                 release=open("Release", "a")
536                 release.write("NotAutomatic: yes")
537                 release.close()
538
539             # Sign if necessary
540             if self.signingkey:
541                 cnf = Config()
542                 keyring = "--secret-keyring \"%s\"" % cnf["Dinstall::SigningKeyring"]
543                 if cnf.has_key("Dinstall::SigningPubKeyring"):
544                     keyring += " --keyring \"%s\"" % cnf["Dinstall::SigningPubKeyring"]
545
546                 os.system("gpg %s --no-options --batch --no-tty --armour --default-key %s --detach-sign -o Release.gpg Release""" % (keyring, self.signingkey))
547
548             # Move the files if we got this far
549             os.rename('Release', os.path.join(bname, 'Release'))
550             if self.signingkey:
551                 os.rename('Release.gpg', os.path.join(bname, 'Release.gpg'))
552
553         # Clean up any left behind files
554         finally:
555             os.chdir(startdir)
556             if fl_fd:
557                 try:
558                     os.close(fl_fd)
559                 except OSError:
560                     pass
561
562             if fl_name:
563                 try:
564                     os.unlink(fl_name)
565                 except OSError:
566                     pass
567
568             if ac_fd:
569                 try:
570                     os.close(ac_fd)
571                 except OSError:
572                     pass
573
574             if ac_name:
575                 try:
576                     os.unlink(ac_name)
577                 except OSError:
578                     pass
579
580     def clean_and_update(self, starttime, Logger, dryrun=False):
581         """WARNING: This routine commits for you"""
582         session = DBConn().session().object_session(self)
583
584         if self.generate_metadata and not dryrun:
585             self.write_metadata(starttime)
586
587         # Grab files older than our execution time
588         older = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter(BuildQueueFile.lastused + timedelta(seconds=self.stay_of_execution) <= starttime).all()
589
590         for o in older:
591             killdb = False
592             try:
593                 if dryrun:
594                     Logger.log(["I: Would have removed %s from the queue" % o.fullpath])
595                 else:
596                     Logger.log(["I: Removing %s from the queue" % o.fullpath])
597                     os.unlink(o.fullpath)
598                     killdb = True
599             except OSError, e:
600                 # If it wasn't there, don't worry
601                 if e.errno == ENOENT:
602                     killdb = True
603                 else:
604                     # TODO: Replace with proper logging call
605                     Logger.log(["E: Could not remove %s" % o.fullpath])
606
607             if killdb:
608                 session.delete(o)
609
610         session.commit()
611
612         for f in os.listdir(self.path):
613             if f.startswith('Packages') or f.startswith('Source') or f.startswith('Release'):
614                 continue
615
616             try:
617                 r = session.query(BuildQueueFile).filter_by(build_queue_id = self.queue_id).filter_by(filename = f).one()
618             except NoResultFound:
619                 fp = os.path.join(self.path, f)
620                 if dryrun:
621                     Logger.log(["I: Would remove unused link %s" % fp])
622                 else:
623                     Logger.log(["I: Removing unused link %s" % fp])
624                     try:
625                         os.unlink(fp)
626                     except OSError:
627                         Logger.log(["E: Failed to unlink unreferenced file %s" % r.fullpath])
628
629     def add_file_from_pool(self, poolfile):
630         """Copies a file into the pool.  Assumes that the PoolFile object is
631         attached to the same SQLAlchemy session as the Queue object is.
632
633         The caller is responsible for committing after calling this function."""
634         poolfile_basename = poolfile.filename[poolfile.filename.rindex(os.sep)+1:]
635
636         # Check if we have a file of this name or this ID already
637         for f in self.queuefiles:
638             if f.fileid is not None and f.fileid == poolfile.file_id or \
639                f.poolfile.filename == poolfile_basename:
640                    # In this case, update the BuildQueueFile entry so we
641                    # don't remove it too early
642                    f.lastused = datetime.now()
643                    DBConn().session().object_session(poolfile).add(f)
644                    return f
645
646         # Prepare BuildQueueFile object
647         qf = BuildQueueFile()
648         qf.build_queue_id = self.queue_id
649         qf.lastused = datetime.now()
650         qf.filename = poolfile_basename
651
652         targetpath = poolfile.fullpath
653         queuepath = os.path.join(self.path, poolfile_basename)
654
655         try:
656             if self.copy_files:
657                 # We need to copy instead of symlink
658                 import utils
659                 utils.copy(targetpath, queuepath)
660                 # NULL in the fileid field implies a copy
661                 qf.fileid = None
662             else:
663                 os.symlink(targetpath, queuepath)
664                 qf.fileid = poolfile.file_id
665         except OSError:
666             return None
667
668         # Get the same session as the PoolFile is using and add the qf to it
669         DBConn().session().object_session(poolfile).add(qf)
670
671         return qf
672
673
674 __all__.append('BuildQueue')
675
676 @session_wrapper
677 def get_build_queue(queuename, session=None):
678     """
679     Returns BuildQueue object for given C{queue name}, creating it if it does not
680     exist.
681
682     @type queuename: string
683     @param queuename: The name of the queue
684
685     @type session: Session
686     @param session: Optional SQLA session object (a temporary one will be
687     generated if not supplied)
688
689     @rtype: BuildQueue
690     @return: BuildQueue object for the given queue
691     """
692
693     q = session.query(BuildQueue).filter_by(queue_name=queuename)
694
695     try:
696         return q.one()
697     except NoResultFound:
698         return None
699
700 __all__.append('get_build_queue')
701
702 ################################################################################
703
704 class BuildQueueFile(object):
705     def __init__(self, *args, **kwargs):
706         pass
707
708     def __repr__(self):
709         return '<BuildQueueFile %s (%s)>' % (self.filename, self.build_queue_id)
710
711     @property
712     def fullpath(self):
713         return os.path.join(self.buildqueue.path, self.filename)
714
715
716 __all__.append('BuildQueueFile')
717
718 ################################################################################
719
720 class ChangePendingBinary(object):
721     def __init__(self, *args, **kwargs):
722         pass
723
724     def __repr__(self):
725         return '<ChangePendingBinary %s>' % self.change_pending_binary_id
726
727 __all__.append('ChangePendingBinary')
728
729 ################################################################################
730
731 class ChangePendingFile(object):
732     def __init__(self, *args, **kwargs):
733         pass
734
735     def __repr__(self):
736         return '<ChangePendingFile %s>' % self.change_pending_file_id
737
738 __all__.append('ChangePendingFile')
739
740 ################################################################################
741
742 class ChangePendingSource(object):
743     def __init__(self, *args, **kwargs):
744         pass
745
746     def __repr__(self):
747         return '<ChangePendingSource %s>' % self.change_pending_source_id
748
749 __all__.append('ChangePendingSource')
750
751 ################################################################################
752
753 class Component(object):
754     def __init__(self, *args, **kwargs):
755         pass
756
757     def __eq__(self, val):
758         if isinstance(val, str):
759             return (self.component_name == val)
760         # This signals to use the normal comparison operator
761         return NotImplemented
762
763     def __ne__(self, val):
764         if isinstance(val, str):
765             return (self.component_name != val)
766         # This signals to use the normal comparison operator
767         return NotImplemented
768
769     def __repr__(self):
770         return '<Component %s>' % self.component_name
771
772
773 __all__.append('Component')
774
775 @session_wrapper
776 def get_component(component, session=None):
777     """
778     Returns database id for given C{component}.
779
780     @type component: string
781     @param component: The name of the override type
782
783     @rtype: int
784     @return: the database id for the given component
785
786     """
787     component = component.lower()
788
789     q = session.query(Component).filter_by(component_name=component)
790
791     try:
792         return q.one()
793     except NoResultFound:
794         return None
795
796 __all__.append('get_component')
797
798 ################################################################################
799
800 class DBConfig(object):
801     def __init__(self, *args, **kwargs):
802         pass
803
804     def __repr__(self):
805         return '<DBConfig %s>' % self.name
806
807 __all__.append('DBConfig')
808
809 ################################################################################
810
811 @session_wrapper
812 def get_or_set_contents_file_id(filename, session=None):
813     """
814     Returns database id for given filename.
815
816     If no matching file is found, a row is inserted.
817
818     @type filename: string
819     @param filename: The filename
820     @type session: SQLAlchemy
821     @param session: Optional SQL session object (a temporary one will be
822     generated if not supplied).  If not passed, a commit will be performed at
823     the end of the function, otherwise the caller is responsible for commiting.
824
825     @rtype: int
826     @return: the database id for the given component
827     """
828
829     q = session.query(ContentFilename).filter_by(filename=filename)
830
831     try:
832         ret = q.one().cafilename_id
833     except NoResultFound:
834         cf = ContentFilename()
835         cf.filename = filename
836         session.add(cf)
837         session.commit_or_flush()
838         ret = cf.cafilename_id
839
840     return ret
841
842 __all__.append('get_or_set_contents_file_id')
843
844 @session_wrapper
845 def get_contents(suite, overridetype, section=None, session=None):
846     """
847     Returns contents for a suite / overridetype combination, limiting
848     to a section if not None.
849
850     @type suite: Suite
851     @param suite: Suite object
852
853     @type overridetype: OverrideType
854     @param overridetype: OverrideType object
855
856     @type section: Section
857     @param section: Optional section object to limit results to
858
859     @type session: SQLAlchemy
860     @param session: Optional SQL session object (a temporary one will be
861     generated if not supplied)
862
863     @rtype: ResultsProxy
864     @return: ResultsProxy object set up to return tuples of (filename, section,
865     package, arch_id)
866     """
867
868     # find me all of the contents for a given suite
869     contents_q = """SELECT (p.path||'/'||n.file) AS fn,
870                             s.section,
871                             b.package,
872                             b.architecture
873                    FROM content_associations c join content_file_paths p ON (c.filepath=p.id)
874                    JOIN content_file_names n ON (c.filename=n.id)
875                    JOIN binaries b ON (b.id=c.binary_pkg)
876                    JOIN override o ON (o.package=b.package)
877                    JOIN section s ON (s.id=o.section)
878                    WHERE o.suite = :suiteid AND o.type = :overridetypeid
879                    AND b.type=:overridetypename"""
880
881     vals = {'suiteid': suite.suite_id,
882             'overridetypeid': overridetype.overridetype_id,
883             'overridetypename': overridetype.overridetype}
884
885     if section is not None:
886         contents_q += " AND s.id = :sectionid"
887         vals['sectionid'] = section.section_id
888
889     contents_q += " ORDER BY fn"
890
891     return session.execute(contents_q, vals)
892
893 __all__.append('get_contents')
894
895 ################################################################################
896
897 class ContentFilepath(object):
898     def __init__(self, *args, **kwargs):
899         pass
900
901     def __repr__(self):
902         return '<ContentFilepath %s>' % self.filepath
903
904 __all__.append('ContentFilepath')
905
906 @session_wrapper
907 def get_or_set_contents_path_id(filepath, session=None):
908     """
909     Returns database id for given path.
910
911     If no matching file is found, a row is inserted.
912
913     @type filepath: string
914     @param filepath: The filepath
915
916     @type session: SQLAlchemy
917     @param session: Optional SQL session object (a temporary one will be
918     generated if not supplied).  If not passed, a commit will be performed at
919     the end of the function, otherwise the caller is responsible for commiting.
920
921     @rtype: int
922     @return: the database id for the given path
923     """
924
925     q = session.query(ContentFilepath).filter_by(filepath=filepath)
926
927     try:
928         ret = q.one().cafilepath_id
929     except NoResultFound:
930         cf = ContentFilepath()
931         cf.filepath = filepath
932         session.add(cf)
933         session.commit_or_flush()
934         ret = cf.cafilepath_id
935
936     return ret
937
938 __all__.append('get_or_set_contents_path_id')
939
940 ################################################################################
941
942 class ContentAssociation(object):
943     def __init__(self, *args, **kwargs):
944         pass
945
946     def __repr__(self):
947         return '<ContentAssociation %s>' % self.ca_id
948
949 __all__.append('ContentAssociation')
950
951 def insert_content_paths(binary_id, fullpaths, session=None):
952     """
953     Make sure given path is associated with given binary id
954
955     @type binary_id: int
956     @param binary_id: the id of the binary
957     @type fullpaths: list
958     @param fullpaths: the list of paths of the file being associated with the binary
959     @type session: SQLAlchemy session
960     @param session: Optional SQLAlchemy session.  If this is passed, the caller
961     is responsible for ensuring a transaction has begun and committing the
962     results or rolling back based on the result code.  If not passed, a commit
963     will be performed at the end of the function, otherwise the caller is
964     responsible for commiting.
965
966     @return: True upon success
967     """
968
969     privatetrans = False
970     if session is None:
971         session = DBConn().session()
972         privatetrans = True
973
974     try:
975         # Insert paths
976         def generate_path_dicts():
977             for fullpath in fullpaths:
978                 if fullpath.startswith( './' ):
979                     fullpath = fullpath[2:]
980
981                 yield {'filename':fullpath, 'id': binary_id }
982
983         for d in generate_path_dicts():
984             session.execute( "INSERT INTO bin_contents ( file, binary_id ) VALUES ( :filename, :id )",
985                          d )
986
987         session.commit()
988         if privatetrans:
989             session.close()
990         return True
991
992     except:
993         traceback.print_exc()
994
995         # Only rollback if we set up the session ourself
996         if privatetrans:
997             session.rollback()
998             session.close()
999
1000         return False
1001
1002 __all__.append('insert_content_paths')
1003
1004 ################################################################################
1005
1006 class DSCFile(object):
1007     def __init__(self, *args, **kwargs):
1008         pass
1009
1010     def __repr__(self):
1011         return '<DSCFile %s>' % self.dscfile_id
1012
1013 __all__.append('DSCFile')
1014
1015 @session_wrapper
1016 def get_dscfiles(dscfile_id=None, source_id=None, poolfile_id=None, session=None):
1017     """
1018     Returns a list of DSCFiles which may be empty
1019
1020     @type dscfile_id: int (optional)
1021     @param dscfile_id: the dscfile_id of the DSCFiles to find
1022
1023     @type source_id: int (optional)
1024     @param source_id: the source id related to the DSCFiles to find
1025
1026     @type poolfile_id: int (optional)
1027     @param poolfile_id: the poolfile id related to the DSCFiles to find
1028
1029     @rtype: list
1030     @return: Possibly empty list of DSCFiles
1031     """
1032
1033     q = session.query(DSCFile)
1034
1035     if dscfile_id is not None:
1036         q = q.filter_by(dscfile_id=dscfile_id)
1037
1038     if source_id is not None:
1039         q = q.filter_by(source_id=source_id)
1040
1041     if poolfile_id is not None:
1042         q = q.filter_by(poolfile_id=poolfile_id)
1043
1044     return q.all()
1045
1046 __all__.append('get_dscfiles')
1047
1048 ################################################################################
1049
1050 class PoolFile(object):
1051     def __init__(self, *args, **kwargs):
1052         pass
1053
1054     def __repr__(self):
1055         return '<PoolFile %s>' % self.filename
1056
1057     @property
1058     def fullpath(self):
1059         return os.path.join(self.location.path, self.filename)
1060
1061 __all__.append('PoolFile')
1062
1063 @session_wrapper
1064 def check_poolfile(filename, filesize, md5sum, location_id, session=None):
1065     """
1066     Returns a tuple:
1067     (ValidFileFound [boolean or None], PoolFile object or None)
1068
1069     @type filename: string
1070     @param filename: the filename of the file to check against the DB
1071
1072     @type filesize: int
1073     @param filesize: the size of the file to check against the DB
1074
1075     @type md5sum: string
1076     @param md5sum: the md5sum of the file to check against the DB
1077
1078     @type location_id: int
1079     @param location_id: the id of the location to look in
1080
1081     @rtype: tuple
1082     @return: Tuple of length 2.
1083                  - If more than one file found with that name: (C{None},  C{None})
1084                  - If valid pool file found: (C{True}, C{PoolFile object})
1085                  - If valid pool file not found:
1086                      - (C{False}, C{None}) if no file found
1087                      - (C{False}, C{PoolFile object}) if file found with size/md5sum mismatch
1088     """
1089
1090     q = session.query(PoolFile).filter_by(filename=filename)
1091     q = q.join(Location).filter_by(location_id=location_id)
1092
1093     ret = None
1094
1095     if q.count() > 1:
1096         ret = (None, None)
1097     elif q.count() < 1:
1098         ret = (False, None)
1099     else:
1100         obj = q.one()
1101         if obj.md5sum != md5sum or obj.filesize != int(filesize):
1102             ret = (False, obj)
1103
1104     if ret is None:
1105         ret = (True, obj)
1106
1107     return ret
1108
1109 __all__.append('check_poolfile')
1110
1111 @session_wrapper
1112 def get_poolfile_by_id(file_id, session=None):
1113     """
1114     Returns a PoolFile objects or None for the given id
1115
1116     @type file_id: int
1117     @param file_id: the id of the file to look for
1118
1119     @rtype: PoolFile or None
1120     @return: either the PoolFile object or None
1121     """
1122
1123     q = session.query(PoolFile).filter_by(file_id=file_id)
1124
1125     try:
1126         return q.one()
1127     except NoResultFound:
1128         return None
1129
1130 __all__.append('get_poolfile_by_id')
1131
1132
1133 @session_wrapper
1134 def get_poolfile_by_name(filename, location_id=None, session=None):
1135     """
1136     Returns an array of PoolFile objects for the given filename and
1137     (optionally) location_id
1138
1139     @type filename: string
1140     @param filename: the filename of the file to check against the DB
1141
1142     @type location_id: int
1143     @param location_id: the id of the location to look in (optional)
1144
1145     @rtype: array
1146     @return: array of PoolFile objects
1147     """
1148
1149     q = session.query(PoolFile).filter_by(filename=filename)
1150
1151     if location_id is not None:
1152         q = q.join(Location).filter_by(location_id=location_id)
1153
1154     return q.all()
1155
1156 __all__.append('get_poolfile_by_name')
1157
1158 @session_wrapper
1159 def get_poolfile_like_name(filename, session=None):
1160     """
1161     Returns an array of PoolFile objects which are like the given name
1162
1163     @type filename: string
1164     @param filename: the filename of the file to check against the DB
1165
1166     @rtype: array
1167     @return: array of PoolFile objects
1168     """
1169
1170     # TODO: There must be a way of properly using bind parameters with %FOO%
1171     q = session.query(PoolFile).filter(PoolFile.filename.like('%%/%s' % filename))
1172
1173     return q.all()
1174
1175 __all__.append('get_poolfile_like_name')
1176
1177 @session_wrapper
1178 def add_poolfile(filename, datadict, location_id, session=None):
1179     """
1180     Add a new file to the pool
1181
1182     @type filename: string
1183     @param filename: filename
1184
1185     @type datadict: dict
1186     @param datadict: dict with needed data
1187
1188     @type location_id: int
1189     @param location_id: database id of the location
1190
1191     @rtype: PoolFile
1192     @return: the PoolFile object created
1193     """
1194     poolfile = PoolFile()
1195     poolfile.filename = filename
1196     poolfile.filesize = datadict["size"]
1197     poolfile.md5sum = datadict["md5sum"]
1198     poolfile.sha1sum = datadict["sha1sum"]
1199     poolfile.sha256sum = datadict["sha256sum"]
1200     poolfile.location_id = location_id
1201
1202     session.add(poolfile)
1203     # Flush to get a file id (NB: This is not a commit)
1204     session.flush()
1205
1206     return poolfile
1207
1208 __all__.append('add_poolfile')
1209
1210 ################################################################################
1211
1212 class Fingerprint(object):
1213     def __init__(self, *args, **kwargs):
1214         pass
1215
1216     def __repr__(self):
1217         return '<Fingerprint %s>' % self.fingerprint
1218
1219 __all__.append('Fingerprint')
1220
1221 @session_wrapper
1222 def get_fingerprint(fpr, session=None):
1223     """
1224     Returns Fingerprint object for given fpr.
1225
1226     @type fpr: string
1227     @param fpr: The fpr to find / add
1228
1229     @type session: SQLAlchemy
1230     @param session: Optional SQL session object (a temporary one will be
1231     generated if not supplied).
1232
1233     @rtype: Fingerprint
1234     @return: the Fingerprint object for the given fpr or None
1235     """
1236
1237     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1238
1239     try:
1240         ret = q.one()
1241     except NoResultFound:
1242         ret = None
1243
1244     return ret
1245
1246 __all__.append('get_fingerprint')
1247
1248 @session_wrapper
1249 def get_or_set_fingerprint(fpr, session=None):
1250     """
1251     Returns Fingerprint object for given fpr.
1252
1253     If no matching fpr is found, a row is inserted.
1254
1255     @type fpr: string
1256     @param fpr: The fpr to find / add
1257
1258     @type session: SQLAlchemy
1259     @param session: Optional SQL session object (a temporary one will be
1260     generated if not supplied).  If not passed, a commit will be performed at
1261     the end of the function, otherwise the caller is responsible for commiting.
1262     A flush will be performed either way.
1263
1264     @rtype: Fingerprint
1265     @return: the Fingerprint object for the given fpr
1266     """
1267
1268     q = session.query(Fingerprint).filter_by(fingerprint=fpr)
1269
1270     try:
1271         ret = q.one()
1272     except NoResultFound:
1273         fingerprint = Fingerprint()
1274         fingerprint.fingerprint = fpr
1275         session.add(fingerprint)
1276         session.commit_or_flush()
1277         ret = fingerprint
1278
1279     return ret
1280
1281 __all__.append('get_or_set_fingerprint')
1282
1283 ################################################################################
1284
1285 # Helper routine for Keyring class
1286 def get_ldap_name(entry):
1287     name = []
1288     for k in ["cn", "mn", "sn"]:
1289         ret = entry.get(k)
1290         if ret and ret[0] != "" and ret[0] != "-":
1291             name.append(ret[0])
1292     return " ".join(name)
1293
1294 ################################################################################
1295
1296 class Keyring(object):
1297     gpg_invocation = "gpg --no-default-keyring --keyring %s" +\
1298                      " --with-colons --fingerprint --fingerprint"
1299
1300     keys = {}
1301     fpr_lookup = {}
1302
1303     def __init__(self, *args, **kwargs):
1304         pass
1305
1306     def __repr__(self):
1307         return '<Keyring %s>' % self.keyring_name
1308
1309     def de_escape_gpg_str(self, txt):
1310         esclist = re.split(r'(\\x..)', txt)
1311         for x in range(1,len(esclist),2):
1312             esclist[x] = "%c" % (int(esclist[x][2:],16))
1313         return "".join(esclist)
1314
1315     def load_keys(self, keyring):
1316         import email.Utils
1317
1318         if not self.keyring_id:
1319             raise Exception('Must be initialized with database information')
1320
1321         k = os.popen(self.gpg_invocation % keyring, "r")
1322         key = None
1323         signingkey = False
1324
1325         for line in k.xreadlines():
1326             field = line.split(":")
1327             if field[0] == "pub":
1328                 key = field[4]
1329                 (name, addr) = email.Utils.parseaddr(field[9])
1330                 name = re.sub(r"\s*[(].*[)]", "", name)
1331                 if name == "" or addr == "" or "@" not in addr:
1332                     name = field[9]
1333                     addr = "invalid-uid"
1334                 name = self.de_escape_gpg_str(name)
1335                 self.keys[key] = {"email": addr}
1336                 if name != "":
1337                     self.keys[key]["name"] = name
1338                 self.keys[key]["aliases"] = [name]
1339                 self.keys[key]["fingerprints"] = []
1340                 signingkey = True
1341             elif key and field[0] == "sub" and len(field) >= 12:
1342                 signingkey = ("s" in field[11])
1343             elif key and field[0] == "uid":
1344                 (name, addr) = email.Utils.parseaddr(field[9])
1345                 if name and name not in self.keys[key]["aliases"]:
1346                     self.keys[key]["aliases"].append(name)
1347             elif signingkey and field[0] == "fpr":
1348                 self.keys[key]["fingerprints"].append(field[9])
1349                 self.fpr_lookup[field[9]] = key
1350
1351     def import_users_from_ldap(self, session):
1352         import ldap
1353         cnf = Config()
1354
1355         LDAPDn = cnf["Import-LDAP-Fingerprints::LDAPDn"]
1356         LDAPServer = cnf["Import-LDAP-Fingerprints::LDAPServer"]
1357
1358         l = ldap.open(LDAPServer)
1359         l.simple_bind_s("","")
1360         Attrs = l.search_s(LDAPDn, ldap.SCOPE_ONELEVEL,
1361                "(&(keyfingerprint=*)(gidnumber=%s))" % (cnf["Import-Users-From-Passwd::ValidGID"]),
1362                ["uid", "keyfingerprint", "cn", "mn", "sn"])
1363
1364         ldap_fin_uid_id = {}
1365
1366         byuid = {}
1367         byname = {}
1368
1369         for i in Attrs:
1370             entry = i[1]
1371             uid = entry["uid"][0]
1372             name = get_ldap_name(entry)
1373             fingerprints = entry["keyFingerPrint"]
1374             keyid = None
1375             for f in fingerprints:
1376                 key = self.fpr_lookup.get(f, None)
1377                 if key not in self.keys:
1378                     continue
1379                 self.keys[key]["uid"] = uid
1380
1381                 if keyid != None:
1382                     continue
1383                 keyid = get_or_set_uid(uid, session).uid_id
1384                 byuid[keyid] = (uid, name)
1385                 byname[uid] = (keyid, name)
1386
1387         return (byname, byuid)
1388
1389     def generate_users_from_keyring(self, format, session):
1390         byuid = {}
1391         byname = {}
1392         any_invalid = False
1393         for x in self.keys.keys():
1394             if self.keys[x]["email"] == "invalid-uid":
1395                 any_invalid = True
1396                 self.keys[x]["uid"] = format % "invalid-uid"
1397             else:
1398                 uid = format % self.keys[x]["email"]
1399                 keyid = get_or_set_uid(uid, session).uid_id
1400                 byuid[keyid] = (uid, self.keys[x]["name"])
1401                 byname[uid] = (keyid, self.keys[x]["name"])
1402                 self.keys[x]["uid"] = uid
1403
1404         if any_invalid:
1405             uid = format % "invalid-uid"
1406             keyid = get_or_set_uid(uid, session).uid_id
1407             byuid[keyid] = (uid, "ungeneratable user id")
1408             byname[uid] = (keyid, "ungeneratable user id")
1409
1410         return (byname, byuid)
1411
1412 __all__.append('Keyring')
1413
1414 @session_wrapper
1415 def get_keyring(keyring, session=None):
1416     """
1417     If C{keyring} does not have an entry in the C{keyrings} table yet, return None
1418     If C{keyring} already has an entry, simply return the existing Keyring
1419
1420     @type keyring: string
1421     @param keyring: the keyring name
1422
1423     @rtype: Keyring
1424     @return: the Keyring object for this keyring
1425     """
1426
1427     q = session.query(Keyring).filter_by(keyring_name=keyring)
1428
1429     try:
1430         return q.one()
1431     except NoResultFound:
1432         return None
1433
1434 __all__.append('get_keyring')
1435
1436 ################################################################################
1437
1438 class KeyringACLMap(object):
1439     def __init__(self, *args, **kwargs):
1440         pass
1441
1442     def __repr__(self):
1443         return '<KeyringACLMap %s>' % self.keyring_acl_map_id
1444
1445 __all__.append('KeyringACLMap')
1446
1447 ################################################################################
1448
1449 class DBChange(object):
1450     def __init__(self, *args, **kwargs):
1451         pass
1452
1453     def __repr__(self):
1454         return '<DBChange %s>' % self.changesname
1455
1456     def clean_from_queue(self):
1457         session = DBConn().session().object_session(self)
1458
1459         # Remove changes_pool_files entries
1460         self.poolfiles = []
1461
1462         # Remove changes_pending_files references
1463         self.files = []
1464
1465         # Clear out of queue
1466         self.in_queue = None
1467         self.approved_for_id = None
1468
1469 __all__.append('DBChange')
1470
1471 @session_wrapper
1472 def get_dbchange(filename, session=None):
1473     """
1474     returns DBChange object for given C{filename}.
1475
1476     @type filename: string
1477     @param filename: the name of the file
1478
1479     @type session: Session
1480     @param session: Optional SQLA session object (a temporary one will be
1481     generated if not supplied)
1482
1483     @rtype: DBChange
1484     @return:  DBChange object for the given filename (C{None} if not present)
1485
1486     """
1487     q = session.query(DBChange).filter_by(changesname=filename)
1488
1489     try:
1490         return q.one()
1491     except NoResultFound:
1492         return None
1493
1494 __all__.append('get_dbchange')
1495
1496 ################################################################################
1497
1498 class Location(object):
1499     def __init__(self, *args, **kwargs):
1500         pass
1501
1502     def __repr__(self):
1503         return '<Location %s (%s)>' % (self.path, self.location_id)
1504
1505 __all__.append('Location')
1506
1507 @session_wrapper
1508 def get_location(location, component=None, archive=None, session=None):
1509     """
1510     Returns Location object for the given combination of location, component
1511     and archive
1512
1513     @type location: string
1514     @param location: the path of the location, e.g. I{/srv/ftp.debian.org/ftp/pool/}
1515
1516     @type component: string
1517     @param component: the component name (if None, no restriction applied)
1518
1519     @type archive: string
1520     @param archive: the archive name (if None, no restriction applied)
1521
1522     @rtype: Location / None
1523     @return: Either a Location object or None if one can't be found
1524     """
1525
1526     q = session.query(Location).filter_by(path=location)
1527
1528     if archive is not None:
1529         q = q.join(Archive).filter_by(archive_name=archive)
1530
1531     if component is not None:
1532         q = q.join(Component).filter_by(component_name=component)
1533
1534     try:
1535         return q.one()
1536     except NoResultFound:
1537         return None
1538
1539 __all__.append('get_location')
1540
1541 ################################################################################
1542
1543 class Maintainer(object):
1544     def __init__(self, *args, **kwargs):
1545         pass
1546
1547     def __repr__(self):
1548         return '''<Maintainer '%s' (%s)>''' % (self.name, self.maintainer_id)
1549
1550     def get_split_maintainer(self):
1551         if not hasattr(self, 'name') or self.name is None:
1552             return ('', '', '', '')
1553
1554         return fix_maintainer(self.name.strip())
1555
1556 __all__.append('Maintainer')
1557
1558 @session_wrapper
1559 def get_or_set_maintainer(name, session=None):
1560     """
1561     Returns Maintainer object for given maintainer name.
1562
1563     If no matching maintainer name is found, a row is inserted.
1564
1565     @type name: string
1566     @param name: The maintainer name to add
1567
1568     @type session: SQLAlchemy
1569     @param session: Optional SQL session object (a temporary one will be
1570     generated if not supplied).  If not passed, a commit will be performed at
1571     the end of the function, otherwise the caller is responsible for commiting.
1572     A flush will be performed either way.
1573
1574     @rtype: Maintainer
1575     @return: the Maintainer object for the given maintainer
1576     """
1577
1578     q = session.query(Maintainer).filter_by(name=name)
1579     try:
1580         ret = q.one()
1581     except NoResultFound:
1582         maintainer = Maintainer()
1583         maintainer.name = name
1584         session.add(maintainer)
1585         session.commit_or_flush()
1586         ret = maintainer
1587
1588     return ret
1589
1590 __all__.append('get_or_set_maintainer')
1591
1592 @session_wrapper
1593 def get_maintainer(maintainer_id, session=None):
1594     """
1595     Return the name of the maintainer behind C{maintainer_id} or None if that
1596     maintainer_id is invalid.
1597
1598     @type maintainer_id: int
1599     @param maintainer_id: the id of the maintainer
1600
1601     @rtype: Maintainer
1602     @return: the Maintainer with this C{maintainer_id}
1603     """
1604
1605     return session.query(Maintainer).get(maintainer_id)
1606
1607 __all__.append('get_maintainer')
1608
1609 ################################################################################
1610
1611 class NewComment(object):
1612     def __init__(self, *args, **kwargs):
1613         pass
1614
1615     def __repr__(self):
1616         return '''<NewComment for '%s %s' (%s)>''' % (self.package, self.version, self.comment_id)
1617
1618 __all__.append('NewComment')
1619
1620 @session_wrapper
1621 def has_new_comment(package, version, session=None):
1622     """
1623     Returns true if the given combination of C{package}, C{version} has a comment.
1624
1625     @type package: string
1626     @param package: name of the package
1627
1628     @type version: string
1629     @param version: package version
1630
1631     @type session: Session
1632     @param session: Optional SQLA session object (a temporary one will be
1633     generated if not supplied)
1634
1635     @rtype: boolean
1636     @return: true/false
1637     """
1638
1639     q = session.query(NewComment)
1640     q = q.filter_by(package=package)
1641     q = q.filter_by(version=version)
1642
1643     return bool(q.count() > 0)
1644
1645 __all__.append('has_new_comment')
1646
1647 @session_wrapper
1648 def get_new_comments(package=None, version=None, comment_id=None, session=None):
1649     """
1650     Returns (possibly empty) list of NewComment objects for the given
1651     parameters
1652
1653     @type package: string (optional)
1654     @param package: name of the package
1655
1656     @type version: string (optional)
1657     @param version: package version
1658
1659     @type comment_id: int (optional)
1660     @param comment_id: An id of a comment
1661
1662     @type session: Session
1663     @param session: Optional SQLA session object (a temporary one will be
1664     generated if not supplied)
1665
1666     @rtype: list
1667     @return: A (possibly empty) list of NewComment objects will be returned
1668     """
1669
1670     q = session.query(NewComment)
1671     if package is not None: q = q.filter_by(package=package)
1672     if version is not None: q = q.filter_by(version=version)
1673     if comment_id is not None: q = q.filter_by(comment_id=comment_id)
1674
1675     return q.all()
1676
1677 __all__.append('get_new_comments')
1678
1679 ################################################################################
1680
1681 class Override(object):
1682     def __init__(self, *args, **kwargs):
1683         pass
1684
1685     def __repr__(self):
1686         return '<Override %s (%s)>' % (self.package, self.suite_id)
1687
1688 __all__.append('Override')
1689
1690 @session_wrapper
1691 def get_override(package, suite=None, component=None, overridetype=None, session=None):
1692     """
1693     Returns Override object for the given parameters
1694
1695     @type package: string
1696     @param package: The name of the package
1697
1698     @type suite: string, list or None
1699     @param suite: The name of the suite (or suites if a list) to limit to.  If
1700                   None, don't limit.  Defaults to None.
1701
1702     @type component: string, list or None
1703     @param component: The name of the component (or components if a list) to
1704                       limit to.  If None, don't limit.  Defaults to None.
1705
1706     @type overridetype: string, list or None
1707     @param overridetype: The name of the overridetype (or overridetypes if a list) to
1708                          limit to.  If None, don't limit.  Defaults to None.
1709
1710     @type session: Session
1711     @param session: Optional SQLA session object (a temporary one will be
1712     generated if not supplied)
1713
1714     @rtype: list
1715     @return: A (possibly empty) list of Override objects will be returned
1716     """
1717
1718     q = session.query(Override)
1719     q = q.filter_by(package=package)
1720
1721     if suite is not None:
1722         if not isinstance(suite, list): suite = [suite]
1723         q = q.join(Suite).filter(Suite.suite_name.in_(suite))
1724
1725     if component is not None:
1726         if not isinstance(component, list): component = [component]
1727         q = q.join(Component).filter(Component.component_name.in_(component))
1728
1729     if overridetype is not None:
1730         if not isinstance(overridetype, list): overridetype = [overridetype]
1731         q = q.join(OverrideType).filter(OverrideType.overridetype.in_(overridetype))
1732
1733     return q.all()
1734
1735 __all__.append('get_override')
1736
1737
1738 ################################################################################
1739
1740 class OverrideType(object):
1741     def __init__(self, *args, **kwargs):
1742         pass
1743
1744     def __repr__(self):
1745         return '<OverrideType %s>' % self.overridetype
1746
1747 __all__.append('OverrideType')
1748
1749 @session_wrapper
1750 def get_override_type(override_type, session=None):
1751     """
1752     Returns OverrideType object for given C{override type}.
1753
1754     @type override_type: string
1755     @param override_type: The name of the override type
1756
1757     @type session: Session
1758     @param session: Optional SQLA session object (a temporary one will be
1759     generated if not supplied)
1760
1761     @rtype: int
1762     @return: the database id for the given override type
1763     """
1764
1765     q = session.query(OverrideType).filter_by(overridetype=override_type)
1766
1767     try:
1768         return q.one()
1769     except NoResultFound:
1770         return None
1771
1772 __all__.append('get_override_type')
1773
1774 ################################################################################
1775
1776 class DebContents(object):
1777     def __init__(self, *args, **kwargs):
1778         pass
1779
1780     def __repr__(self):
1781         return '<DebConetnts %s: %s>' % (self.package.package,self.file)
1782
1783 __all__.append('DebContents')
1784
1785
1786 class UdebContents(object):
1787     def __init__(self, *args, **kwargs):
1788         pass
1789
1790     def __repr__(self):
1791         return '<UdebConetnts %s: %s>' % (self.package.package,self.file)
1792
1793 __all__.append('UdebContents')
1794
1795 class PendingBinContents(object):
1796     def __init__(self, *args, **kwargs):
1797         pass
1798
1799     def __repr__(self):
1800         return '<PendingBinContents %s>' % self.contents_id
1801
1802 __all__.append('PendingBinContents')
1803
1804 def insert_pending_content_paths(package,
1805                                  is_udeb,
1806                                  fullpaths,
1807                                  session=None):
1808     """
1809     Make sure given paths are temporarily associated with given
1810     package
1811
1812     @type package: dict
1813     @param package: the package to associate with should have been read in from the binary control file
1814     @type fullpaths: list
1815     @param fullpaths: the list of paths of the file being associated with the binary
1816     @type session: SQLAlchemy session
1817     @param session: Optional SQLAlchemy session.  If this is passed, the caller
1818     is responsible for ensuring a transaction has begun and committing the
1819     results or rolling back based on the result code.  If not passed, a commit
1820     will be performed at the end of the function
1821
1822     @return: True upon success, False if there is a problem
1823     """
1824
1825     privatetrans = False
1826
1827     if session is None:
1828         session = DBConn().session()
1829         privatetrans = True
1830
1831     try:
1832         arch = get_architecture(package['Architecture'], session)
1833         arch_id = arch.arch_id
1834
1835         # Remove any already existing recorded files for this package
1836         q = session.query(PendingBinContents)
1837         q = q.filter_by(package=package['Package'])
1838         q = q.filter_by(version=package['Version'])
1839         q = q.filter_by(architecture=arch_id)
1840         q.delete()
1841
1842         for fullpath in fullpaths:
1843
1844             if fullpath.startswith( "./" ):
1845                 fullpath = fullpath[2:]
1846
1847             pca = PendingBinContents()
1848             pca.package = package['Package']
1849             pca.version = package['Version']
1850             pca.file = fullpath
1851             pca.architecture = arch_id
1852
1853             if isudeb:
1854                 pca.type = 8 # gross
1855             else:
1856                 pca.type = 7 # also gross
1857             session.add(pca)
1858
1859         # Only commit if we set up the session ourself
1860         if privatetrans:
1861             session.commit()
1862             session.close()
1863         else:
1864             session.flush()
1865
1866         return True
1867     except Exception, e:
1868         traceback.print_exc()
1869
1870         # Only rollback if we set up the session ourself
1871         if privatetrans:
1872             session.rollback()
1873             session.close()
1874
1875         return False
1876
1877 __all__.append('insert_pending_content_paths')
1878
1879 ################################################################################
1880
1881 class PolicyQueue(object):
1882     def __init__(self, *args, **kwargs):
1883         pass
1884
1885     def __repr__(self):
1886         return '<PolicyQueue %s>' % self.queue_name
1887
1888 __all__.append('PolicyQueue')
1889
1890 @session_wrapper
1891 def get_policy_queue(queuename, session=None):
1892     """
1893     Returns PolicyQueue object for given C{queue name}
1894
1895     @type queuename: string
1896     @param queuename: The name of the queue
1897
1898     @type session: Session
1899     @param session: Optional SQLA session object (a temporary one will be
1900     generated if not supplied)
1901
1902     @rtype: PolicyQueue
1903     @return: PolicyQueue object for the given queue
1904     """
1905
1906     q = session.query(PolicyQueue).filter_by(queue_name=queuename)
1907
1908     try:
1909         return q.one()
1910     except NoResultFound:
1911         return None
1912
1913 __all__.append('get_policy_queue')
1914
1915 ################################################################################
1916
1917 class Priority(object):
1918     def __init__(self, *args, **kwargs):
1919         pass
1920
1921     def __eq__(self, val):
1922         if isinstance(val, str):
1923             return (self.priority == val)
1924         # This signals to use the normal comparison operator
1925         return NotImplemented
1926
1927     def __ne__(self, val):
1928         if isinstance(val, str):
1929             return (self.priority != val)
1930         # This signals to use the normal comparison operator
1931         return NotImplemented
1932
1933     def __repr__(self):
1934         return '<Priority %s (%s)>' % (self.priority, self.priority_id)
1935
1936 __all__.append('Priority')
1937
1938 @session_wrapper
1939 def get_priority(priority, session=None):
1940     """
1941     Returns Priority object for given C{priority name}.
1942
1943     @type priority: string
1944     @param priority: The name of the priority
1945
1946     @type session: Session
1947     @param session: Optional SQLA session object (a temporary one will be
1948     generated if not supplied)
1949
1950     @rtype: Priority
1951     @return: Priority object for the given priority
1952     """
1953
1954     q = session.query(Priority).filter_by(priority=priority)
1955
1956     try:
1957         return q.one()
1958     except NoResultFound:
1959         return None
1960
1961 __all__.append('get_priority')
1962
1963 @session_wrapper
1964 def get_priorities(session=None):
1965     """
1966     Returns dictionary of priority names -> id mappings
1967
1968     @type session: Session
1969     @param session: Optional SQL session object (a temporary one will be
1970     generated if not supplied)
1971
1972     @rtype: dictionary
1973     @return: dictionary of priority names -> id mappings
1974     """
1975
1976     ret = {}
1977     q = session.query(Priority)
1978     for x in q.all():
1979         ret[x.priority] = x.priority_id
1980
1981     return ret
1982
1983 __all__.append('get_priorities')
1984
1985 ################################################################################
1986
1987 class Section(object):
1988     def __init__(self, *args, **kwargs):
1989         pass
1990
1991     def __eq__(self, val):
1992         if isinstance(val, str):
1993             return (self.section == val)
1994         # This signals to use the normal comparison operator
1995         return NotImplemented
1996
1997     def __ne__(self, val):
1998         if isinstance(val, str):
1999             return (self.section != val)
2000         # This signals to use the normal comparison operator
2001         return NotImplemented
2002
2003     def __repr__(self):
2004         return '<Section %s>' % self.section
2005
2006 __all__.append('Section')
2007
2008 @session_wrapper
2009 def get_section(section, session=None):
2010     """
2011     Returns Section object for given C{section name}.
2012
2013     @type section: string
2014     @param section: The name of the section
2015
2016     @type session: Session
2017     @param session: Optional SQLA session object (a temporary one will be
2018     generated if not supplied)
2019
2020     @rtype: Section
2021     @return: Section object for the given section name
2022     """
2023
2024     q = session.query(Section).filter_by(section=section)
2025
2026     try:
2027         return q.one()
2028     except NoResultFound:
2029         return None
2030
2031 __all__.append('get_section')
2032
2033 @session_wrapper
2034 def get_sections(session=None):
2035     """
2036     Returns dictionary of section names -> id mappings
2037
2038     @type session: Session
2039     @param session: Optional SQL session object (a temporary one will be
2040     generated if not supplied)
2041
2042     @rtype: dictionary
2043     @return: dictionary of section names -> id mappings
2044     """
2045
2046     ret = {}
2047     q = session.query(Section)
2048     for x in q.all():
2049         ret[x.section] = x.section_id
2050
2051     return ret
2052
2053 __all__.append('get_sections')
2054
2055 ################################################################################
2056
2057 class DBSource(object):
2058     def __init__(self, *args, **kwargs):
2059         pass
2060
2061     def __repr__(self):
2062         return '<DBSource %s (%s)>' % (self.source, self.version)
2063
2064 __all__.append('DBSource')
2065
2066 @session_wrapper
2067 def source_exists(source, source_version, suites = ["any"], session=None):
2068     """
2069     Ensure that source exists somewhere in the archive for the binary
2070     upload being processed.
2071       1. exact match     => 1.0-3
2072       2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
2073
2074     @type source: string
2075     @param source: source name
2076
2077     @type source_version: string
2078     @param source_version: expected source version
2079
2080     @type suites: list
2081     @param suites: list of suites to check in, default I{any}
2082
2083     @type session: Session
2084     @param session: Optional SQLA session object (a temporary one will be
2085     generated if not supplied)
2086
2087     @rtype: int
2088     @return: returns 1 if a source with expected version is found, otherwise 0
2089
2090     """
2091
2092     cnf = Config()
2093     ret = 1
2094
2095     for suite in suites:
2096         q = session.query(DBSource).filter_by(source=source)
2097         if suite != "any":
2098             # source must exist in suite X, or in some other suite that's
2099             # mapped to X, recursively... silent-maps are counted too,
2100             # unreleased-maps aren't.
2101             maps = cnf.ValueList("SuiteMappings")[:]
2102             maps.reverse()
2103             maps = [ m.split() for m in maps ]
2104             maps = [ (x[1], x[2]) for x in maps
2105                             if x[0] == "map" or x[0] == "silent-map" ]
2106             s = [suite]
2107             for x in maps:
2108                 if x[1] in s and x[0] not in s:
2109                     s.append(x[0])
2110
2111             q = q.join(SrcAssociation).join(Suite)
2112             q = q.filter(Suite.suite_name.in_(s))
2113
2114         # Reduce the query results to a list of version numbers
2115         ql = [ j.version for j in q.all() ]
2116
2117         # Try (1)
2118         if source_version in ql:
2119             continue
2120
2121         # Try (2)
2122         from daklib.regexes import re_bin_only_nmu
2123         orig_source_version = re_bin_only_nmu.sub('', source_version)
2124         if orig_source_version in ql:
2125             continue
2126
2127         # No source found so return not ok
2128         ret = 0
2129
2130     return ret
2131
2132 __all__.append('source_exists')
2133
2134 @session_wrapper
2135 def get_suites_source_in(source, session=None):
2136     """
2137     Returns list of Suite objects which given C{source} name is in
2138
2139     @type source: str
2140     @param source: DBSource package name to search for
2141
2142     @rtype: list
2143     @return: list of Suite objects for the given source
2144     """
2145
2146     return session.query(Suite).join(SrcAssociation).join(DBSource).filter_by(source=source).all()
2147
2148 __all__.append('get_suites_source_in')
2149
2150 @session_wrapper
2151 def get_sources_from_name(source, version=None, dm_upload_allowed=None, session=None):
2152     """
2153     Returns list of DBSource objects for given C{source} name and other parameters
2154
2155     @type source: str
2156     @param source: DBSource package name to search for
2157
2158     @type version: str or None
2159     @param version: DBSource version name to search for or None if not applicable
2160
2161     @type dm_upload_allowed: bool
2162     @param dm_upload_allowed: If None, no effect.  If True or False, only
2163     return packages with that dm_upload_allowed setting
2164
2165     @type session: Session
2166     @param session: Optional SQL session object (a temporary one will be
2167     generated if not supplied)
2168
2169     @rtype: list
2170     @return: list of DBSource objects for the given name (may be empty)
2171     """
2172
2173     q = session.query(DBSource).filter_by(source=source)
2174
2175     if version is not None:
2176         q = q.filter_by(version=version)
2177
2178     if dm_upload_allowed is not None:
2179         q = q.filter_by(dm_upload_allowed=dm_upload_allowed)
2180
2181     return q.all()
2182
2183 __all__.append('get_sources_from_name')
2184
2185 @session_wrapper
2186 def get_source_in_suite(source, suite, session=None):
2187     """
2188     Returns list of DBSource objects for a combination of C{source} and C{suite}.
2189
2190       - B{source} - source package name, eg. I{mailfilter}, I{bbdb}, I{glibc}
2191       - B{suite} - a suite name, eg. I{unstable}
2192
2193     @type source: string
2194     @param source: source package name
2195
2196     @type suite: string
2197     @param suite: the suite name
2198
2199     @rtype: string
2200     @return: the version for I{source} in I{suite}
2201
2202     """
2203
2204     q = session.query(SrcAssociation)
2205     q = q.join('source').filter_by(source=source)
2206     q = q.join('suite').filter_by(suite_name=suite)
2207
2208     try:
2209         return q.one().source
2210     except NoResultFound:
2211         return None
2212
2213 __all__.append('get_source_in_suite')
2214
2215 @session_wrapper
2216 def get_all_sources_in_suite(suitename, session=None):
2217     """
2218     Returns list of sources and versions found in a given suite
2219
2220       - B{suite} - a suite name, eg. I{unstable}
2221
2222     @type suite: string
2223     @param suite: the suite name
2224
2225     @rtype: dictionary
2226     @return: the version for I{source} in I{suite}
2227
2228     """
2229     query = """SELECT source, version FROM source_suite
2230     WHERE suite_name=:suitename
2231     ORDER BY source"""
2232
2233     vals = {'suitename': suitename}
2234    
2235     return session.execute(query, vals)
2236
2237 __all__.append('get_all_sources_in_suite')
2238
2239 ################################################################################
2240
2241 @session_wrapper
2242 def add_dsc_to_db(u, filename, session=None):
2243     entry = u.pkg.files[filename]
2244     source = DBSource()
2245     pfs = []
2246
2247     source.source = u.pkg.dsc["source"]
2248     source.version = u.pkg.dsc["version"] # NB: not files[file]["version"], that has no epoch
2249     source.maintainer_id = get_or_set_maintainer(u.pkg.dsc["maintainer"], session).maintainer_id
2250     source.changedby_id = get_or_set_maintainer(u.pkg.changes["changed-by"], session).maintainer_id
2251     source.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2252     source.install_date = datetime.now().date()
2253
2254     dsc_component = entry["component"]
2255     dsc_location_id = entry["location id"]
2256
2257     source.dm_upload_allowed = (u.pkg.dsc.get("dm-upload-allowed", '') == "yes")
2258
2259     # Set up a new poolfile if necessary
2260     if not entry.has_key("files id") or not entry["files id"]:
2261         filename = entry["pool name"] + filename
2262         poolfile = add_poolfile(filename, entry, dsc_location_id, session)
2263         session.flush()
2264         pfs.append(poolfile)
2265         entry["files id"] = poolfile.file_id
2266
2267     source.poolfile_id = entry["files id"]
2268     session.add(source)
2269     session.flush()
2270
2271     for suite_name in u.pkg.changes["distribution"].keys():
2272         sa = SrcAssociation()
2273         sa.source_id = source.source_id
2274         sa.suite_id = get_suite(suite_name).suite_id
2275         session.add(sa)
2276
2277     session.flush()
2278
2279     # Add the source files to the DB (files and dsc_files)
2280     dscfile = DSCFile()
2281     dscfile.source_id = source.source_id
2282     dscfile.poolfile_id = entry["files id"]
2283     session.add(dscfile)
2284
2285     for dsc_file, dentry in u.pkg.dsc_files.items():
2286         df = DSCFile()
2287         df.source_id = source.source_id
2288
2289         # If the .orig tarball is already in the pool, it's
2290         # files id is stored in dsc_files by check_dsc().
2291         files_id = dentry.get("files id", None)
2292
2293         # Find the entry in the files hash
2294         # TODO: Bail out here properly
2295         dfentry = None
2296         for f, e in u.pkg.files.items():
2297             if f == dsc_file:
2298                 dfentry = e
2299                 break
2300
2301         if files_id is None:
2302             filename = dfentry["pool name"] + dsc_file
2303
2304             (found, obj) = check_poolfile(filename, dentry["size"], dentry["md5sum"], dsc_location_id)
2305             # FIXME: needs to check for -1/-2 and or handle exception
2306             if found and obj is not None:
2307                 files_id = obj.file_id
2308                 pfs.append(obj)
2309
2310             # If still not found, add it
2311             if files_id is None:
2312                 # HACK: Force sha1sum etc into dentry
2313                 dentry["sha1sum"] = dfentry["sha1sum"]
2314                 dentry["sha256sum"] = dfentry["sha256sum"]
2315                 poolfile = add_poolfile(filename, dentry, dsc_location_id, session)
2316                 pfs.append(poolfile)
2317                 files_id = poolfile.file_id
2318         else:
2319             poolfile = get_poolfile_by_id(files_id, session)
2320             if poolfile is None:
2321                 utils.fubar("INTERNAL ERROR. Found no poolfile with id %d" % files_id)
2322             pfs.append(poolfile)
2323
2324         df.poolfile_id = files_id
2325         session.add(df)
2326
2327     session.flush()
2328
2329     # Add the src_uploaders to the DB
2330     uploader_ids = [source.maintainer_id]
2331     if u.pkg.dsc.has_key("uploaders"):
2332         for up in u.pkg.dsc["uploaders"].replace(">, ", ">\t").split("\t"):
2333             up = up.strip()
2334             uploader_ids.append(get_or_set_maintainer(up, session).maintainer_id)
2335
2336     added_ids = {}
2337     for up_id in uploader_ids:
2338         if added_ids.has_key(up_id):
2339             import utils
2340             utils.warn("Already saw uploader %s for source %s" % (up_id, source.source))
2341             continue
2342
2343         added_ids[up_id]=1
2344
2345         su = SrcUploader()
2346         su.maintainer_id = up_id
2347         su.source_id = source.source_id
2348         session.add(su)
2349
2350     session.flush()
2351
2352     return source, dsc_component, dsc_location_id, pfs
2353
2354 __all__.append('add_dsc_to_db')
2355
2356 @session_wrapper
2357 def add_deb_to_db(u, filename, session=None):
2358     """
2359     Contrary to what you might expect, this routine deals with both
2360     debs and udebs.  That info is in 'dbtype', whilst 'type' is
2361     'deb' for both of them
2362     """
2363     cnf = Config()
2364     entry = u.pkg.files[filename]
2365
2366     bin = DBBinary()
2367     bin.package = entry["package"]
2368     bin.version = entry["version"]
2369     bin.maintainer_id = get_or_set_maintainer(entry["maintainer"], session).maintainer_id
2370     bin.fingerprint_id = get_or_set_fingerprint(u.pkg.changes["fingerprint"], session).fingerprint_id
2371     bin.arch_id = get_architecture(entry["architecture"], session).arch_id
2372     bin.binarytype = entry["dbtype"]
2373
2374     # Find poolfile id
2375     filename = entry["pool name"] + filename
2376     fullpath = os.path.join(cnf["Dir::Pool"], filename)
2377     if not entry.get("location id", None):
2378         entry["location id"] = get_location(cnf["Dir::Pool"], entry["component"], session=session).location_id
2379
2380     if entry.get("files id", None):
2381         poolfile = get_poolfile_by_id(bin.poolfile_id)
2382         bin.poolfile_id = entry["files id"]
2383     else:
2384         poolfile = add_poolfile(filename, entry, entry["location id"], session)
2385         bin.poolfile_id = entry["files id"] = poolfile.file_id
2386
2387     # Find source id
2388     bin_sources = get_sources_from_name(entry["source package"], entry["source version"], session=session)
2389     if len(bin_sources) != 1:
2390         raise NoSourceFieldError, "Unable to find a unique source id for %s (%s), %s, file %s, type %s, signed by %s" % \
2391                                   (bin.package, bin.version, bin.architecture.arch_string,
2392                                    filename, bin.binarytype, u.pkg.changes["fingerprint"])
2393
2394     bin.source_id = bin_sources[0].source_id
2395
2396     # Add and flush object so it has an ID
2397     session.add(bin)
2398     session.flush()
2399
2400     # Add BinAssociations
2401     for suite_name in u.pkg.changes["distribution"].keys():
2402         ba = BinAssociation()
2403         ba.binary_id = bin.binary_id
2404         ba.suite_id = get_suite(suite_name).suite_id
2405         session.add(ba)
2406
2407     session.flush()
2408
2409     # Deal with contents - disabled for now
2410     #contents = copy_temporary_contents(bin.package, bin.version, bin.architecture.arch_string, os.path.basename(filename), None, session)
2411     #if not contents:
2412     #    print "REJECT\nCould not determine contents of package %s" % bin.package
2413     #    session.rollback()
2414     #    raise MissingContents, "No contents stored for package %s, and couldn't determine contents of %s" % (bin.package, filename)
2415
2416     return poolfile
2417
2418 __all__.append('add_deb_to_db')
2419
2420 ################################################################################
2421
2422 class SourceACL(object):
2423     def __init__(self, *args, **kwargs):
2424         pass
2425
2426     def __repr__(self):
2427         return '<SourceACL %s>' % self.source_acl_id
2428
2429 __all__.append('SourceACL')
2430
2431 ################################################################################
2432
2433 class SrcAssociation(object):
2434     def __init__(self, *args, **kwargs):
2435         pass
2436
2437     def __repr__(self):
2438         return '<SrcAssociation %s (%s, %s)>' % (self.sa_id, self.source, self.suite)
2439
2440 __all__.append('SrcAssociation')
2441
2442 ################################################################################
2443
2444 class SrcFormat(object):
2445     def __init__(self, *args, **kwargs):
2446         pass
2447
2448     def __repr__(self):
2449         return '<SrcFormat %s>' % (self.format_name)
2450
2451 __all__.append('SrcFormat')
2452
2453 ################################################################################
2454
2455 class SrcUploader(object):
2456     def __init__(self, *args, **kwargs):
2457         pass
2458
2459     def __repr__(self):
2460         return '<SrcUploader %s>' % self.uploader_id
2461
2462 __all__.append('SrcUploader')
2463
2464 ################################################################################
2465
2466 SUITE_FIELDS = [ ('SuiteName', 'suite_name'),
2467                  ('SuiteID', 'suite_id'),
2468                  ('Version', 'version'),
2469                  ('Origin', 'origin'),
2470                  ('Label', 'label'),
2471                  ('Description', 'description'),
2472                  ('Untouchable', 'untouchable'),
2473                  ('Announce', 'announce'),
2474                  ('Codename', 'codename'),
2475                  ('OverrideCodename', 'overridecodename'),
2476                  ('ValidTime', 'validtime'),
2477                  ('Priority', 'priority'),
2478                  ('NotAutomatic', 'notautomatic'),
2479                  ('CopyChanges', 'copychanges'),
2480                  ('CopyDotDak', 'copydotdak'),
2481                  ('CommentsDir', 'commentsdir'),
2482                  ('OverrideSuite', 'overridesuite'),
2483                  ('ChangelogBase', 'changelogbase')]
2484
2485
2486 class Suite(object):
2487     def __init__(self, *args, **kwargs):
2488         pass
2489
2490     def __repr__(self):
2491         return '<Suite %s>' % self.suite_name
2492
2493     def __eq__(self, val):
2494         if isinstance(val, str):
2495             return (self.suite_name == val)
2496         # This signals to use the normal comparison operator
2497         return NotImplemented
2498
2499     def __ne__(self, val):
2500         if isinstance(val, str):
2501             return (self.suite_name != val)
2502         # This signals to use the normal comparison operator
2503         return NotImplemented
2504
2505     def details(self):
2506         ret = []
2507         for disp, field in SUITE_FIELDS:
2508             val = getattr(self, field, None)
2509             if val is not None:
2510                 ret.append("%s: %s" % (disp, val))
2511
2512         return "\n".join(ret)
2513
2514 __all__.append('Suite')
2515
2516 @session_wrapper
2517 def get_suite_architecture(suite, architecture, session=None):
2518     """
2519     Returns a SuiteArchitecture object given C{suite} and ${arch} or None if it
2520     doesn't exist
2521
2522     @type suite: str
2523     @param suite: Suite name to search for
2524
2525     @type architecture: str
2526     @param architecture: Architecture name to search for
2527
2528     @type session: Session
2529     @param session: Optional SQL session object (a temporary one will be
2530     generated if not supplied)
2531
2532     @rtype: SuiteArchitecture
2533     @return: the SuiteArchitecture object or None
2534     """
2535
2536     q = session.query(SuiteArchitecture)
2537     q = q.join(Architecture).filter_by(arch_string=architecture)
2538     q = q.join(Suite).filter_by(suite_name=suite)
2539
2540     try:
2541         return q.one()
2542     except NoResultFound:
2543         return None
2544
2545 __all__.append('get_suite_architecture')
2546
2547 @session_wrapper
2548 def get_suite(suite, session=None):
2549     """
2550     Returns Suite object for given C{suite name}.
2551
2552     @type suite: string
2553     @param suite: The name of the suite
2554
2555     @type session: Session
2556     @param session: Optional SQLA session object (a temporary one will be
2557     generated if not supplied)
2558
2559     @rtype: Suite
2560     @return: Suite object for the requested suite name (None if not present)
2561     """
2562
2563     q = session.query(Suite).filter_by(suite_name=suite)
2564
2565     try:
2566         return q.one()
2567     except NoResultFound:
2568         return None
2569
2570 __all__.append('get_suite')
2571
2572 ################################################################################
2573
2574 class SuiteArchitecture(object):
2575     def __init__(self, *args, **kwargs):
2576         pass
2577
2578     def __repr__(self):
2579         return '<SuiteArchitecture (%s, %s)>' % (self.suite_id, self.arch_id)
2580
2581 __all__.append('SuiteArchitecture')
2582
2583 @session_wrapper
2584 def get_suite_architectures(suite, skipsrc=False, skipall=False, session=None):
2585     """
2586     Returns list of Architecture objects for given C{suite} name
2587
2588     @type suite: str
2589     @param suite: Suite name to search for
2590
2591     @type skipsrc: boolean
2592     @param skipsrc: Whether to skip returning the 'source' architecture entry
2593     (Default False)
2594
2595     @type skipall: boolean
2596     @param skipall: Whether to skip returning the 'all' architecture entry
2597     (Default False)
2598
2599     @type session: Session
2600     @param session: Optional SQL session object (a temporary one will be
2601     generated if not supplied)
2602
2603     @rtype: list
2604     @return: list of Architecture objects for the given name (may be empty)
2605     """
2606
2607     q = session.query(Architecture)
2608     q = q.join(SuiteArchitecture)
2609     q = q.join(Suite).filter_by(suite_name=suite)
2610
2611     if skipsrc:
2612         q = q.filter(Architecture.arch_string != 'source')
2613
2614     if skipall:
2615         q = q.filter(Architecture.arch_string != 'all')
2616
2617     q = q.order_by('arch_string')
2618
2619     return q.all()
2620
2621 __all__.append('get_suite_architectures')
2622
2623 ################################################################################
2624
2625 class SuiteSrcFormat(object):
2626     def __init__(self, *args, **kwargs):
2627         pass
2628
2629     def __repr__(self):
2630         return '<SuiteSrcFormat (%s, %s)>' % (self.suite_id, self.src_format_id)
2631
2632 __all__.append('SuiteSrcFormat')
2633
2634 @session_wrapper
2635 def get_suite_src_formats(suite, session=None):
2636     """
2637     Returns list of allowed SrcFormat for C{suite}.
2638
2639     @type suite: str
2640     @param suite: Suite name to search for
2641
2642     @type session: Session
2643     @param session: Optional SQL session object (a temporary one will be
2644     generated if not supplied)
2645
2646     @rtype: list
2647     @return: the list of allowed source formats for I{suite}
2648     """
2649
2650     q = session.query(SrcFormat)
2651     q = q.join(SuiteSrcFormat)
2652     q = q.join(Suite).filter_by(suite_name=suite)
2653     q = q.order_by('format_name')
2654
2655     return q.all()
2656
2657 __all__.append('get_suite_src_formats')
2658
2659 ################################################################################
2660
2661 class Uid(object):
2662     def __init__(self, *args, **kwargs):
2663         pass
2664
2665     def __eq__(self, val):
2666         if isinstance(val, str):
2667             return (self.uid == val)
2668         # This signals to use the normal comparison operator
2669         return NotImplemented
2670
2671     def __ne__(self, val):
2672         if isinstance(val, str):
2673             return (self.uid != val)
2674         # This signals to use the normal comparison operator
2675         return NotImplemented
2676
2677     def __repr__(self):
2678         return '<Uid %s (%s)>' % (self.uid, self.name)
2679
2680 __all__.append('Uid')
2681
2682 @session_wrapper
2683 def add_database_user(uidname, session=None):
2684     """
2685     Adds a database user
2686
2687     @type uidname: string
2688     @param uidname: The uid of the user to add
2689
2690     @type session: SQLAlchemy
2691     @param session: Optional SQL session object (a temporary one will be
2692     generated if not supplied).  If not passed, a commit will be performed at
2693     the end of the function, otherwise the caller is responsible for commiting.
2694
2695     @rtype: Uid
2696     @return: the uid object for the given uidname
2697     """
2698
2699     session.execute("CREATE USER :uid", {'uid': uidname})
2700     session.commit_or_flush()
2701
2702 __all__.append('add_database_user')
2703
2704 @session_wrapper
2705 def get_or_set_uid(uidname, session=None):
2706     """
2707     Returns uid object for given uidname.
2708
2709     If no matching uidname is found, a row is inserted.
2710
2711     @type uidname: string
2712     @param uidname: The uid to add
2713
2714     @type session: SQLAlchemy
2715     @param session: Optional SQL session object (a temporary one will be
2716     generated if not supplied).  If not passed, a commit will be performed at
2717     the end of the function, otherwise the caller is responsible for commiting.
2718
2719     @rtype: Uid
2720     @return: the uid object for the given uidname
2721     """
2722
2723     q = session.query(Uid).filter_by(uid=uidname)
2724
2725     try:
2726         ret = q.one()
2727     except NoResultFound:
2728         uid = Uid()
2729         uid.uid = uidname
2730         session.add(uid)
2731         session.commit_or_flush()
2732         ret = uid
2733
2734     return ret
2735
2736 __all__.append('get_or_set_uid')
2737
2738 @session_wrapper
2739 def get_uid_from_fingerprint(fpr, session=None):
2740     q = session.query(Uid)
2741     q = q.join(Fingerprint).filter_by(fingerprint=fpr)
2742
2743     try:
2744         return q.one()
2745     except NoResultFound:
2746         return None
2747
2748 __all__.append('get_uid_from_fingerprint')
2749
2750 ################################################################################
2751
2752 class UploadBlock(object):
2753     def __init__(self, *args, **kwargs):
2754         pass
2755
2756     def __repr__(self):
2757         return '<UploadBlock %s (%s)>' % (self.source, self.upload_block_id)
2758
2759 __all__.append('UploadBlock')
2760
2761 ################################################################################
2762
2763 class DBConn(object):
2764     """
2765     database module init.
2766     """
2767     __shared_state = {}
2768
2769     def __init__(self, *args, **kwargs):
2770         self.__dict__ = self.__shared_state
2771
2772         if not getattr(self, 'initialised', False):
2773             self.initialised = True
2774             self.debug = kwargs.has_key('debug')
2775             self.__createconn()
2776
2777     def __setuptables(self):
2778         tables = (
2779             'architecture',
2780             'archive',
2781             'bin_associations',
2782             'binaries',
2783             'binary_acl',
2784             'binary_acl_map',
2785             'bin_contents',
2786             'build_queue',
2787             'build_queue_files',
2788             'component',
2789             'config',
2790             'changes_pending_binaries',
2791             'changes_pending_files',
2792             'changes_pending_files_map',
2793             'changes_pending_source',
2794             'changes_pending_source_files',
2795             'changes_pool_files',
2796             'deb_contents',
2797             'dsc_files',
2798             'files',
2799             'fingerprint',
2800             'keyrings',
2801             'changes',
2802             'keyring_acl_map',
2803             'location',
2804             'maintainer',
2805             'new_comments',
2806             'override',
2807             'override_type',
2808             'pending_bin_contents',
2809             'policy_queue',
2810             'priority',
2811             'section',
2812             'source',
2813             'source_acl',
2814             'src_associations',
2815             'src_format',
2816             'src_uploaders',
2817             'suite',
2818             'suite_architectures',
2819             'suite_src_formats',
2820             'suite_build_queue_copy',
2821             'udeb_contents',
2822             'uid',
2823             'upload_blocks',
2824         )
2825
2826         for table_name in tables:
2827             table = Table(table_name, self.db_meta, autoload=True)
2828             setattr(self, 'tbl_%s' % table_name, table)
2829
2830     def __setupmappers(self):
2831         mapper(Architecture, self.tbl_architecture,
2832                properties = dict(arch_id = self.tbl_architecture.c.id))
2833
2834         mapper(Archive, self.tbl_archive,
2835                properties = dict(archive_id = self.tbl_archive.c.id,
2836                                  archive_name = self.tbl_archive.c.name))
2837
2838         mapper(BinAssociation, self.tbl_bin_associations,
2839                properties = dict(ba_id = self.tbl_bin_associations.c.id,
2840                                  suite_id = self.tbl_bin_associations.c.suite,
2841                                  suite = relation(Suite),
2842                                  binary_id = self.tbl_bin_associations.c.bin,
2843                                  binary = relation(DBBinary)))
2844
2845         mapper(PendingBinContents, self.tbl_pending_bin_contents,
2846                properties = dict(contents_id =self.tbl_pending_bin_contents.c.id,
2847                                  filename = self.tbl_pending_bin_contents.c.filename,
2848                                  package = self.tbl_pending_bin_contents.c.package,
2849                                  version = self.tbl_pending_bin_contents.c.version,
2850                                  arch = self.tbl_pending_bin_contents.c.arch,
2851                                  otype = self.tbl_pending_bin_contents.c.type))
2852
2853         mapper(DebContents, self.tbl_deb_contents,
2854                properties = dict(binary_id=self.tbl_deb_contents.c.binary_id,
2855                                  package=self.tbl_deb_contents.c.package,
2856                                  suite=self.tbl_deb_contents.c.suite,
2857                                  arch=self.tbl_deb_contents.c.arch,
2858                                  section=self.tbl_deb_contents.c.section,
2859                                  filename=self.tbl_deb_contents.c.filename))
2860
2861         mapper(UdebContents, self.tbl_udeb_contents,
2862                properties = dict(binary_id=self.tbl_udeb_contents.c.binary_id,
2863                                  package=self.tbl_udeb_contents.c.package,
2864                                  suite=self.tbl_udeb_contents.c.suite,
2865                                  arch=self.tbl_udeb_contents.c.arch,
2866                                  section=self.tbl_udeb_contents.c.section,
2867                                  filename=self.tbl_udeb_contents.c.filename))
2868
2869         mapper(BuildQueue, self.tbl_build_queue,
2870                properties = dict(queue_id = self.tbl_build_queue.c.id))
2871
2872         mapper(BuildQueueFile, self.tbl_build_queue_files,
2873                properties = dict(buildqueue = relation(BuildQueue, backref='queuefiles'),
2874                                  poolfile = relation(PoolFile, backref='buildqueueinstances')))
2875
2876         mapper(DBBinary, self.tbl_binaries,
2877                properties = dict(binary_id = self.tbl_binaries.c.id,
2878                                  package = self.tbl_binaries.c.package,
2879                                  version = self.tbl_binaries.c.version,
2880                                  maintainer_id = self.tbl_binaries.c.maintainer,
2881                                  maintainer = relation(Maintainer),
2882                                  source_id = self.tbl_binaries.c.source,
2883                                  source = relation(DBSource),
2884                                  arch_id = self.tbl_binaries.c.architecture,
2885                                  architecture = relation(Architecture),
2886                                  poolfile_id = self.tbl_binaries.c.file,
2887                                  poolfile = relation(PoolFile),
2888                                  binarytype = self.tbl_binaries.c.type,
2889                                  fingerprint_id = self.tbl_binaries.c.sig_fpr,
2890                                  fingerprint = relation(Fingerprint),
2891                                  install_date = self.tbl_binaries.c.install_date,
2892                                  binassociations = relation(BinAssociation,
2893                                                             primaryjoin=(self.tbl_binaries.c.id==self.tbl_bin_associations.c.bin))))
2894
2895         mapper(BinaryACL, self.tbl_binary_acl,
2896                properties = dict(binary_acl_id = self.tbl_binary_acl.c.id))
2897
2898         mapper(BinaryACLMap, self.tbl_binary_acl_map,
2899                properties = dict(binary_acl_map_id = self.tbl_binary_acl_map.c.id,
2900                                  fingerprint = relation(Fingerprint, backref="binary_acl_map"),
2901                                  architecture = relation(Architecture)))
2902
2903         mapper(Component, self.tbl_component,
2904                properties = dict(component_id = self.tbl_component.c.id,
2905                                  component_name = self.tbl_component.c.name))
2906
2907         mapper(DBConfig, self.tbl_config,
2908                properties = dict(config_id = self.tbl_config.c.id))
2909
2910         mapper(DSCFile, self.tbl_dsc_files,
2911                properties = dict(dscfile_id = self.tbl_dsc_files.c.id,
2912                                  source_id = self.tbl_dsc_files.c.source,
2913                                  source = relation(DBSource),
2914                                  poolfile_id = self.tbl_dsc_files.c.file,
2915                                  poolfile = relation(PoolFile)))
2916
2917         mapper(PoolFile, self.tbl_files,
2918                properties = dict(file_id = self.tbl_files.c.id,
2919                                  filesize = self.tbl_files.c.size,
2920                                  location_id = self.tbl_files.c.location,
2921                                  location = relation(Location)))
2922
2923         mapper(Fingerprint, self.tbl_fingerprint,
2924                properties = dict(fingerprint_id = self.tbl_fingerprint.c.id,
2925                                  uid_id = self.tbl_fingerprint.c.uid,
2926                                  uid = relation(Uid),
2927                                  keyring_id = self.tbl_fingerprint.c.keyring,
2928                                  keyring = relation(Keyring),
2929                                  source_acl = relation(SourceACL),
2930                                  binary_acl = relation(BinaryACL)))
2931
2932         mapper(Keyring, self.tbl_keyrings,
2933                properties = dict(keyring_name = self.tbl_keyrings.c.name,
2934                                  keyring_id = self.tbl_keyrings.c.id))
2935
2936         mapper(DBChange, self.tbl_changes,
2937                properties = dict(change_id = self.tbl_changes.c.id,
2938                                  poolfiles = relation(PoolFile,
2939                                                       secondary=self.tbl_changes_pool_files,
2940                                                       backref="changeslinks"),
2941                                  seen = self.tbl_changes.c.seen,
2942                                  source = self.tbl_changes.c.source,
2943                                  binaries = self.tbl_changes.c.binaries,
2944                                  architecture = self.tbl_changes.c.architecture,
2945                                  distribution = self.tbl_changes.c.distribution,
2946                                  urgency = self.tbl_changes.c.urgency,
2947                                  maintainer = self.tbl_changes.c.maintainer,
2948                                  changedby = self.tbl_changes.c.changedby,
2949                                  date = self.tbl_changes.c.date,
2950                                  version = self.tbl_changes.c.version,
2951                                  files = relation(ChangePendingFile,
2952                                                   secondary=self.tbl_changes_pending_files_map,
2953                                                   backref="changesfile"),
2954                                  in_queue_id = self.tbl_changes.c.in_queue,
2955                                  in_queue = relation(PolicyQueue,
2956                                                      primaryjoin=(self.tbl_changes.c.in_queue==self.tbl_policy_queue.c.id)),
2957                                  approved_for_id = self.tbl_changes.c.approved_for))
2958
2959         mapper(ChangePendingBinary, self.tbl_changes_pending_binaries,
2960                properties = dict(change_pending_binary_id = self.tbl_changes_pending_binaries.c.id))
2961
2962         mapper(ChangePendingFile, self.tbl_changes_pending_files,
2963                properties = dict(change_pending_file_id = self.tbl_changes_pending_files.c.id,
2964                                  filename = self.tbl_changes_pending_files.c.filename,
2965                                  size = self.tbl_changes_pending_files.c.size,
2966                                  md5sum = self.tbl_changes_pending_files.c.md5sum,
2967                                  sha1sum = self.tbl_changes_pending_files.c.sha1sum,
2968                                  sha256sum = self.tbl_changes_pending_files.c.sha256sum))
2969
2970         mapper(ChangePendingSource, self.tbl_changes_pending_source,
2971                properties = dict(change_pending_source_id = self.tbl_changes_pending_source.c.id,
2972                                  change = relation(DBChange),
2973                                  maintainer = relation(Maintainer,
2974                                                        primaryjoin=(self.tbl_changes_pending_source.c.maintainer_id==self.tbl_maintainer.c.id)),
2975                                  changedby = relation(Maintainer,
2976                                                       primaryjoin=(self.tbl_changes_pending_source.c.changedby_id==self.tbl_maintainer.c.id)),
2977                                  fingerprint = relation(Fingerprint),
2978                                  source_files = relation(ChangePendingFile,
2979                                                          secondary=self.tbl_changes_pending_source_files,
2980                                                          backref="pending_sources")))
2981
2982
2983         mapper(KeyringACLMap, self.tbl_keyring_acl_map,
2984                properties = dict(keyring_acl_map_id = self.tbl_keyring_acl_map.c.id,
2985                                  keyring = relation(Keyring, backref="keyring_acl_map"),
2986                                  architecture = relation(Architecture)))
2987
2988         mapper(Location, self.tbl_location,
2989                properties = dict(location_id = self.tbl_location.c.id,
2990                                  component_id = self.tbl_location.c.component,
2991                                  component = relation(Component),
2992                                  archive_id = self.tbl_location.c.archive,
2993                                  archive = relation(Archive),
2994                                  archive_type = self.tbl_location.c.type))
2995
2996         mapper(Maintainer, self.tbl_maintainer,
2997                properties = dict(maintainer_id = self.tbl_maintainer.c.id))
2998
2999         mapper(NewComment, self.tbl_new_comments,
3000                properties = dict(comment_id = self.tbl_new_comments.c.id))
3001
3002         mapper(Override, self.tbl_override,
3003                properties = dict(suite_id = self.tbl_override.c.suite,
3004                                  suite = relation(Suite),
3005                                  package = self.tbl_override.c.package,
3006                                  component_id = self.tbl_override.c.component,
3007                                  component = relation(Component),
3008                                  priority_id = self.tbl_override.c.priority,
3009                                  priority = relation(Priority),
3010                                  section_id = self.tbl_override.c.section,
3011                                  section = relation(Section),
3012                                  overridetype_id = self.tbl_override.c.type,
3013                                  overridetype = relation(OverrideType)))
3014
3015         mapper(OverrideType, self.tbl_override_type,
3016                properties = dict(overridetype = self.tbl_override_type.c.type,
3017                                  overridetype_id = self.tbl_override_type.c.id))
3018
3019         mapper(PolicyQueue, self.tbl_policy_queue,
3020                properties = dict(policy_queue_id = self.tbl_policy_queue.c.id))
3021
3022         mapper(Priority, self.tbl_priority,
3023                properties = dict(priority_id = self.tbl_priority.c.id))
3024
3025         mapper(Section, self.tbl_section,
3026                properties = dict(section_id = self.tbl_section.c.id,
3027                                  section=self.tbl_section.c.section))
3028
3029         mapper(DBSource, self.tbl_source,
3030                properties = dict(source_id = self.tbl_source.c.id,
3031                                  version = self.tbl_source.c.version,
3032                                  maintainer_id = self.tbl_source.c.maintainer,
3033                                  maintainer = relation(Maintainer,
3034                                                        primaryjoin=(self.tbl_source.c.maintainer==self.tbl_maintainer.c.id)),
3035                                  poolfile_id = self.tbl_source.c.file,
3036                                  poolfile = relation(PoolFile),
3037                                  fingerprint_id = self.tbl_source.c.sig_fpr,
3038                                  fingerprint = relation(Fingerprint),
3039                                  changedby_id = self.tbl_source.c.changedby,
3040                                  changedby = relation(Maintainer,
3041                                                       primaryjoin=(self.tbl_source.c.changedby==self.tbl_maintainer.c.id)),
3042                                  srcfiles = relation(DSCFile,
3043                                                      primaryjoin=(self.tbl_source.c.id==self.tbl_dsc_files.c.source)),
3044                                  srcassociations = relation(SrcAssociation,
3045                                                             primaryjoin=(self.tbl_source.c.id==self.tbl_src_associations.c.source)),
3046                                  srcuploaders = relation(SrcUploader)))
3047
3048         mapper(SourceACL, self.tbl_source_acl,
3049                properties = dict(source_acl_id = self.tbl_source_acl.c.id))
3050
3051         mapper(SrcAssociation, self.tbl_src_associations,
3052                properties = dict(sa_id = self.tbl_src_associations.c.id,
3053                                  suite_id = self.tbl_src_associations.c.suite,
3054                                  suite = relation(Suite),
3055                                  source_id = self.tbl_src_associations.c.source,
3056                                  source = relation(DBSource)))
3057
3058         mapper(SrcFormat, self.tbl_src_format,
3059                properties = dict(src_format_id = self.tbl_src_format.c.id,
3060                                  format_name = self.tbl_src_format.c.format_name))
3061
3062         mapper(SrcUploader, self.tbl_src_uploaders,
3063                properties = dict(uploader_id = self.tbl_src_uploaders.c.id,
3064                                  source_id = self.tbl_src_uploaders.c.source,
3065                                  source = relation(DBSource,
3066                                                    primaryjoin=(self.tbl_src_uploaders.c.source==self.tbl_source.c.id)),
3067                                  maintainer_id = self.tbl_src_uploaders.c.maintainer,
3068                                  maintainer = relation(Maintainer,
3069                                                        primaryjoin=(self.tbl_src_uploaders.c.maintainer==self.tbl_maintainer.c.id))))
3070
3071         mapper(Suite, self.tbl_suite,
3072                properties = dict(suite_id = self.tbl_suite.c.id,
3073                                  policy_queue = relation(PolicyQueue),
3074                                  copy_queues = relation(BuildQueue, secondary=self.tbl_suite_build_queue_copy)))
3075
3076         mapper(SuiteArchitecture, self.tbl_suite_architectures,
3077                properties = dict(suite_id = self.tbl_suite_architectures.c.suite,
3078                                  suite = relation(Suite, backref='suitearchitectures'),
3079                                  arch_id = self.tbl_suite_architectures.c.architecture,
3080                                  architecture = relation(Architecture)))
3081
3082         mapper(SuiteSrcFormat, self.tbl_suite_src_formats,
3083                properties = dict(suite_id = self.tbl_suite_src_formats.c.suite,
3084                                  suite = relation(Suite, backref='suitesrcformats'),
3085                                  src_format_id = self.tbl_suite_src_formats.c.src_format,
3086                                  src_format = relation(SrcFormat)))
3087
3088         mapper(Uid, self.tbl_uid,
3089                properties = dict(uid_id = self.tbl_uid.c.id,
3090                                  fingerprint = relation(Fingerprint)))
3091
3092         mapper(UploadBlock, self.tbl_upload_blocks,
3093                properties = dict(upload_block_id = self.tbl_upload_blocks.c.id,
3094                                  fingerprint = relation(Fingerprint, backref="uploadblocks"),
3095                                  uid = relation(Uid, backref="uploadblocks")))
3096
3097     ## Connection functions
3098     def __createconn(self):
3099         from config import Config
3100         cnf = Config()
3101         if cnf["DB::Host"]:
3102             # TCP/IP
3103             connstr = "postgres://%s" % cnf["DB::Host"]
3104             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3105                 connstr += ":%s" % cnf["DB::Port"]
3106             connstr += "/%s" % cnf["DB::Name"]
3107         else:
3108             # Unix Socket
3109             connstr = "postgres:///%s" % cnf["DB::Name"]
3110             if cnf["DB::Port"] and cnf["DB::Port"] != "-1":
3111                 connstr += "?port=%s" % cnf["DB::Port"]
3112
3113         self.db_pg   = create_engine(connstr, echo=self.debug)
3114         self.db_meta = MetaData()
3115         self.db_meta.bind = self.db_pg
3116         self.db_smaker = sessionmaker(bind=self.db_pg,
3117                                       autoflush=True,
3118                                       autocommit=False)
3119
3120         self.__setuptables()
3121         self.__setupmappers()
3122
3123     def session(self):
3124         return self.db_smaker()
3125
3126 __all__.append('DBConn')
3127
3128