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