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