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