]> git.decadent.org.uk Git - dak.git/blob - daklib/contents.py
Implement and test class ContentsWriter.
[dak.git] / daklib / contents.py
1 #!/usr/bin/env python
2 """
3 Helper code for contents generation.
4
5 @contact: Debian FTPMaster <ftpmaster@debian.org>
6 @copyright: 2011 Torsten Werner <twerner@debian.org>
7 @license: GNU General Public License version 2 or later
8 """
9
10 ################################################################################
11
12 # This program is free software; you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation; either version 2 of the License, or
15 # (at your option) any later version.
16
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
26 ################################################################################
27
28 from daklib.dbconn import *
29 from daklib.config import Config
30
31 from sqlalchemy import desc, or_
32 from subprocess import Popen, PIPE
33
34 class ContentsWriter(object):
35     '''
36     ContentsWriter writes the Contents-$arch.gz files.
37     '''
38     def __init__(self, suite, architecture, overridetype, component = None):
39         '''
40         The constructor clones its arguments into a new session object to make
41         sure that the new ContentsWriter object can be executed in a different
42         thread.
43         '''
44         self.suite = suite.clone()
45         self.session = self.suite.session()
46         self.architecture = architecture.clone(self.session)
47         self.overridetype = overridetype.clone(self.session)
48         if component is not None:
49             self.component = component.clone(self.session)
50         else:
51             self.component = None
52
53     def query(self):
54         '''
55         Returns a query object that is doing most of the work.
56         '''
57         params = {
58             'suite':    self.suite.suite_id,
59             'arch_all': get_architecture('all', self.session).arch_id,
60             'arch':     self.architecture.arch_id,
61             'type_id':  self.overridetype.overridetype_id,
62             'type':     self.overridetype.overridetype,
63         }
64
65         if self.component is not None:
66             params['component'] = component.component_id
67             sql = '''
68 create temp table newest_binaries (
69     id integer primary key,
70     package text);
71
72 create index newest_binaries_by_package on newest_binaries (package);
73
74 insert into newest_binaries (id, package)
75     select distinct on (package) id, package from binaries
76         where type = :type and
77             (architecture = :arch_all or architecture = :arch) and
78             id in (select bin from bin_associations where suite = :suite)
79         order by package, version desc;
80
81 with
82
83 unique_override as
84     (select o.package, s.section
85         from override o, section s
86         where o.suite = :suite and o.type = :type_id and o.section = s.id and
87         o.component = :component)
88
89 select bc.file, substring(o.section from position('/' in o.section) + 1) || '/' || b.package as package
90     from newest_binaries b, bin_contents bc, unique_override o
91     where b.id = bc.binary_id and o.package = b.package
92     order by bc.file, b.package'''
93
94         else:
95             sql = '''
96 create temp table newest_binaries (
97     id integer primary key,
98     package text);
99
100 create index newest_binaries_by_package on newest_binaries (package);
101
102 insert into newest_binaries (id, package)
103     select distinct on (package) id, package from binaries
104         where type = :type and
105             (architecture = :arch_all or architecture = :arch) and
106             id in (select bin from bin_associations where suite = :suite)
107         order by package, version desc;
108
109 with
110
111 unique_override as
112     (select distinct on (o.package, s.section) o.package, s.section
113         from override o, section s
114         where o.suite = :suite and o.type = :type_id and o.section = s.id
115         order by o.package, s.section, o.modified desc)
116
117 select bc.file, substring(o.section from position('/' in o.section) + 1) || '/' || b.package as package
118     from newest_binaries b, bin_contents bc, unique_override o
119     where b.id = bc.binary_id and o.package = b.package
120     order by bc.file, b.package'''
121
122         return self.session.query("file", "package").from_statement(sql). \
123             params(params)
124
125     def formatline(self, filename, package_list):
126         '''
127         Returns a formatted string for the filename argument.
128         '''
129         package_list = ','.join(package_list)
130         return "%-60s%s\n" % (filename, package_list)
131
132     def fetch(self):
133         '''
134         Yields a new line of the Contents-$arch.gz file in filename order.
135         '''
136         last_filename = None
137         package_list = []
138         for filename, package in self.query().yield_per(100):
139             if filename != last_filename:
140                 if last_filename is not None:
141                     yield self.formatline(last_filename, package_list)
142                 last_filename = filename
143                 package_list = []
144             package_list.append(package)
145         yield self.formatline(last_filename, package_list)
146         # end transaction to return connection to pool
147         self.session.rollback()
148
149     def get_list(self):
150         '''
151         Returns a list of lines for the Contents-$arch.gz file.
152         '''
153         return [item for item in self.fetch()]
154
155     def output_filename(self):
156         '''
157         Returns the name of the output file.
158         '''
159         values = {
160             'root': Config()['Dir::Root'],
161             'suite': self.suite.suite_name,
162             'architecture': self.architecture.arch_string
163         }
164         if self.component is None:
165             return "%(root)s%(suite)s/Contents-%(architecture)s.gz" % values
166         values['component'] = self.component.component_name
167         return "%(root)s%(suite)s/%(component)s/Contents-%(architecture)s.gz" % values
168
169     def write_file(self):
170         '''
171         Write the output file.
172         '''
173         command = ['gzip', '--rsyncable']
174         output_file = open(self.output_filename(), 'w')
175         pipe = Popen(command, stdin = PIPE, stdout = output_file).stdin
176         for item in self.fetch():
177             pipe.write(item)
178         pipe.close()
179         output_file.close()