timeout watchfile and homepage downloads
[pet/pet3.git] / pet / classifier.py
1 # vim:ts=2:sw=2:et:ai:sts=2
2 # Copyright 2011, Ansgar Burchardt <ansgar@debian.org>
3 #
4 # Permission to use, copy, modify, and/or distribute this software for any
5 # purpose with or without fee is hereby granted, provided that the above
6 # copyright notice and this permission notice appear in all copies.
7 #
8 # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15
16 from pet.models import *
17
18 import apt_pkg
19 import sqlalchemy
20 import sqlalchemy.orm
21
22 class ClassifiedPackage(object):
23   def __init__(self, named_tree, bugs, suite_packages, tags):
24     self.named_tree = named_tree
25     self.bugs = bugs
26     self.suite_packages = suite_packages
27     self.tags = tags
28     self.watch = self.named_tree.watch_result
29   name = property(lambda self: self.named_tree.package.name)
30   source = property(lambda self: self.named_tree.source)
31   version = property(lambda self: self.named_tree.version)
32   distribution = property(lambda self: self.named_tree.distribution)
33   last_changed_by = property(lambda self: self.named_tree.last_changed_by)
34   last_changed = property(lambda self: self.named_tree.last_changed)
35   todo = property(lambda self: self.named_tree.todo)
36
37   @property
38   def has_rc_bugs(self):
39     for b in self.bugs:
40       if b.severity in ('serious', 'grave', 'critical'):
41         return True
42     return False
43
44   @property
45   def ready_for_upload(self):
46     highest_tag = self.highest_tag
47     if self.distribution is not None and self.distribution != 'UNRELEASED' and not self.is_tagged and not self.is_in_archive:
48       return True
49     return False
50
51   @property
52   def is_tagged(self):
53     for t in self.tags:
54       if t.version == self.version:
55         return True
56     return False
57
58   @property
59   def missing_tag(self):
60     if self.is_in_archive and not self.is_tagged:
61       return True
62     return False
63
64   @property
65   def is_in_archive(self):
66     for sp in self.suite_packages:
67       if sp.version == self.version:
68         return True
69     return False
70
71   @property
72   def highest_tag(self):
73     try:
74       return self.tags[0]
75     except IndexError:
76       return None
77
78   @property
79   def highest_archive(self):
80     try:
81       return self.suite_packages[0]
82     except IndexError:
83       return None
84
85   @property
86   def todo_bugs(self):
87     for b in self.bugs:
88       if not b.forwarded and 'fixed-upstream' not in b.tags and 'pending' not in b.tags and 'wontfix' not in b.tags and 'moreinfo' not in b.tags:
89         return True
90     return False
91
92   @property
93   def newer_upstream(self):
94     if self.watch and self.watch.upstream_version:
95       return apt_pkg.version_compare(self.watch.upstream_version,
96           self.watch.debian_version) > 0
97     return False
98
99   @property
100   def watch_problem(self):
101     if self.watch:
102       if self.watch.error is not None:
103         return True
104       if apt_pkg.version_compare(self.watch.upstream_version,
105           self.watch.debian_version) < 0:
106         return True
107     return False
108
109 class Classifier(object):
110   def __init__(self, session, named_trees, suite_condition, bug_tracker_condition):
111     self.session = session
112     sorted_named_trees = named_trees.join(NamedTree.package) \
113         .options(sqlalchemy.orm.joinedload(NamedTree.watch_result)) \
114         .order_by(Package.name, Package.repository_id, Package.id)
115
116     bug_sources = session.query(BugSource) \
117         .join(BugSource.bug).join((NamedTree, BugSource.source == NamedTree.source)) \
118         .filter(NamedTree.id.in_(named_trees.from_self(NamedTree.id).subquery())) \
119         .filter(bug_tracker_condition) \
120         .order_by(Bug.severity.desc(), Bug.bug_number) \
121         .filter(Bug.done == False) \
122         .options(sqlalchemy.orm.joinedload(BugSource.bug))
123     bugs = {}
124     for bs in bug_sources:
125       bugs.setdefault(bs.source, []).append(bs.bug)
126
127     suite_packages_query = session.query(SuitePackage).join(SuitePackage.suite) \
128         .join((NamedTree, SuitePackage.source == NamedTree.source)) \
129         .filter(NamedTree.id.in_(named_trees.from_self(NamedTree.id).subquery())) \
130         .filter(suite_condition) \
131         .order_by(SuitePackage.version.desc()) \
132         .options(sqlalchemy.orm.joinedload(SuitePackage.suite))
133     suite_packages = {}
134     for sp in suite_packages_query:
135       suite_packages.setdefault(sp.source, []).append(sp)
136
137     Tags = sqlalchemy.orm.aliased(NamedTree)
138     Reference = sqlalchemy.orm.aliased(NamedTree)
139     max_version = session.query(sqlalchemy.func.max(Reference.version)) \
140         .filter(Reference.type == 'tag') \
141         .filter(Reference.package_id == Tags.package_id) \
142         .correlate(Tags) \
143         .as_scalar()
144
145     tags_query = session.query(Tags) \
146         .filter(Tags.type == 'tag') \
147         .filter(Tags.version == max_version) \
148         .order_by(Tags.package_id, Tags.version.desc()) \
149         .filter(Tags.package_id.in_(named_trees.from_self(NamedTree.package_id).subquery()))
150     tags = {}
151     for t in tags_query:
152       tags.setdefault(t.package_id, []).append(t)
153
154     self.packages = []
155     for nt in sorted_named_trees:
156       self.packages.append(ClassifiedPackage(nt, bugs.get(nt.source, []), suite_packages.get(nt.source, []), tags.get(nt.package_id, [])))
157
158   def classify(self):
159     classified = dict()
160     for p in self.packages:
161       if p.ready_for_upload:
162         cls = 'ready_for_upload'
163       elif p.has_rc_bugs:
164         cls = 'rc_bugs'
165       elif p.missing_tag:
166         cls = 'missing_tag'
167       elif not p.tags:
168         cls = 'new'
169       elif p.newer_upstream:
170         cls = 'new_upstream'
171       elif p.watch_problem:
172         cls = 'watch_problem'
173       elif p.bugs:
174         cls = 'bugs'
175       elif not p.is_tagged:
176         cls = 'wip'
177       else:
178         cls = 'other'
179       classified.setdefault(cls, []).append(p)
180     return classified
181   def classes(self):
182     return [
183       { 'name': "Ready For Upload", 'key': 'ready_for_upload' },
184       { 'name': "Packages with RC bugs", 'key': 'rc_bugs' },
185       { 'name': "Missing tags", 'key': 'missing_tag' },
186       { 'name': "Newer upstream version", 'key': 'new_upstream' },
187       { 'name': 'Problems with debian/watch', 'key': 'watch_problem' },
188       { 'name': 'New packages', 'key': 'new' },
189       { 'name': 'With bugs', 'key': 'bugs' },
190       { 'name': 'Work in progress', 'key': 'wip' },
191       #{ 'name': "Other packages", 'key': 'other' },
192       ]