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