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