]> git.decadent.org.uk Git - dak.git/blob - daklib/contents.py
Add new class ContentsScanner.
[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 from daklib.threadpool import ThreadPool
31
32 from sqlalchemy import desc, or_
33 from subprocess import Popen, PIPE
34
35 class ContentsWriter(object):
36     '''
37     ContentsWriter writes the Contents-$arch.gz files.
38     '''
39     def __init__(self, suite, architecture, overridetype, component = None):
40         '''
41         The constructor clones its arguments into a new session object to make
42         sure that the new ContentsWriter object can be executed in a different
43         thread.
44         '''
45         self.suite = suite.clone()
46         self.session = self.suite.session()
47         self.architecture = architecture.clone(self.session)
48         self.overridetype = overridetype.clone(self.session)
49         if component is not None:
50             self.component = component.clone(self.session)
51         else:
52             self.component = None
53
54     def query(self):
55         '''
56         Returns a query object that is doing most of the work.
57         '''
58         params = {
59             'suite':    self.suite.suite_id,
60             'arch_all': get_architecture('all', self.session).arch_id,
61             'arch':     self.architecture.arch_id,
62             'type_id':  self.overridetype.overridetype_id,
63             'type':     self.overridetype.overridetype,
64         }
65
66         if self.component is not None:
67             params['component'] = component.component_id
68             sql = '''
69 create temp table newest_binaries (
70     id integer primary key,
71     package text);
72
73 create index newest_binaries_by_package on newest_binaries (package);
74
75 insert into newest_binaries (id, package)
76     select distinct on (package) id, package from binaries
77         where type = :type and
78             (architecture = :arch_all or architecture = :arch) and
79             id in (select bin from bin_associations where suite = :suite)
80         order by package, version desc;
81
82 with
83
84 unique_override as
85     (select o.package, s.section
86         from override o, section s
87         where o.suite = :suite and o.type = :type_id and o.section = s.id and
88         o.component = :component)
89
90 select bc.file, substring(o.section from position('/' in o.section) + 1) || '/' || b.package as package
91     from newest_binaries b, bin_contents bc, unique_override o
92     where b.id = bc.binary_id and o.package = b.package
93     order by bc.file, b.package'''
94
95         else:
96             sql = '''
97 create temp table newest_binaries (
98     id integer primary key,
99     package text);
100
101 create index newest_binaries_by_package on newest_binaries (package);
102
103 insert into newest_binaries (id, package)
104     select distinct on (package) id, package from binaries
105         where type = :type and
106             (architecture = :arch_all or architecture = :arch) and
107             id in (select bin from bin_associations where suite = :suite)
108         order by package, version desc;
109
110 with
111
112 unique_override as
113     (select distinct on (o.package, s.section) o.package, s.section
114         from override o, section s
115         where o.suite = :suite and o.type = :type_id and o.section = s.id
116         order by o.package, s.section, o.modified desc)
117
118 select bc.file, substring(o.section from position('/' in o.section) + 1) || '/' || b.package as package
119     from newest_binaries b, bin_contents bc, unique_override o
120     where b.id = bc.binary_id and o.package = b.package
121     order by bc.file, b.package'''
122
123         return self.session.query("file", "package").from_statement(sql). \
124             params(params)
125
126     def formatline(self, filename, package_list):
127         '''
128         Returns a formatted string for the filename argument.
129         '''
130         package_list = ','.join(package_list)
131         return "%-60s%s\n" % (filename, package_list)
132
133     def fetch(self):
134         '''
135         Yields a new line of the Contents-$arch.gz file in filename order.
136         '''
137         last_filename = None
138         package_list = []
139         for filename, package in self.query().yield_per(100):
140             if filename != last_filename:
141                 if last_filename is not None:
142                     yield self.formatline(last_filename, package_list)
143                 last_filename = filename
144                 package_list = []
145             package_list.append(package)
146         yield self.formatline(last_filename, package_list)
147         # end transaction to return connection to pool
148         self.session.rollback()
149
150     def get_list(self):
151         '''
152         Returns a list of lines for the Contents-$arch.gz file.
153         '''
154         return [item for item in self.fetch()]
155
156     def output_filename(self):
157         '''
158         Returns the name of the output file.
159         '''
160         values = {
161             'root': Config()['Dir::Root'],
162             'suite': self.suite.suite_name,
163             'architecture': self.architecture.arch_string
164         }
165         if self.component is None:
166             return "%(root)s%(suite)s/Contents-%(architecture)s.gz" % values
167         values['component'] = self.component.component_name
168         return "%(root)s%(suite)s/%(component)s/Contents-%(architecture)s.gz" % values
169
170     def write_file(self):
171         '''
172         Write the output file.
173         '''
174         command = ['gzip', '--rsyncable']
175         output_file = open(self.output_filename(), 'w')
176         pipe = Popen(command, stdin = PIPE, stdout = output_file).stdin
177         for item in self.fetch():
178             pipe.write(item)
179         pipe.close()
180         output_file.close()
181
182
183 class ContentsScanner(object):
184     '''
185     ContentsScanner provides a threadsafe method scan() to scan the contents of
186     a DBBinary object.
187     '''
188     def __init__(self, binary):
189         '''
190         The argument binary is the actual DBBinary object that should be
191         scanned.
192         '''
193         self.binary_id = binary.binary_id
194
195     def scan(self, dummy_arg = None):
196         '''
197         This method does the actual scan and fills in the associated BinContents
198         property. It commits any changes to the database. The argument dummy_arg
199         is ignored but needed by our threadpool implementation.
200         '''
201         session = DBConn().session()
202         binary = session.query(DBBinary).get(self.binary_id)
203         for filename in binary.scan_contents():
204             binary.contents.append(BinContents(file = filename))
205         session.commit()
206         session.close()
207
208     @classmethod
209     def scan_all(class_, limit = None):
210         '''
211         The class method scan_all() scans all binaries using multiple threads.
212         The number of binaries to be scanned can be limited with the limit
213         argument.
214         '''
215         session = DBConn().session()
216         query = session.query(DBBinary)
217         if limit is not None:
218             query = query.limit(limit)
219         threadpool = ThreadPool()
220         for binary in query.yield_per(100):
221             threadpool.queueTask(ContentsScanner(binary).scan)
222         threadpool.joinAll()
223         session.close()