]> git.decadent.org.uk Git - dak.git/blob - tools/queue_rss.py
Merge commit 'ftpmaster/master'
[dak.git] / tools / queue_rss.py
1 #!/usr/bin/python
2 # Generate two rss feeds for a directory with .changes file
3
4 # License: GPL v2 or later
5 # Author: Filippo Giunchedi <filippo@debian.org>
6 # Version: 0.4
7
8 import os
9 import os.path
10 import cPickle
11 import sys
12 import encodings.ascii
13 from optparse import OptionParser
14 from datetime import datetime
15
16 import PyRSS2Gen
17
18 from debian_bundle.deb822 import Changes
19
20 inrss_filename = "NEW_in.rss"
21 outrss_filename = "NEW_out.rss"
22 db_filename = "status.db"
23
24 parser = OptionParser()
25 parser.set_defaults(queuedir="queue", outdir="out", datadir="status", max_entries="30")
26
27 parser.add_option("-q", "--queuedir", dest="queuedir",
28         help="The queue dir (%default)")
29 parser.add_option("-o", "--outdir", dest="outdir",
30         help="The output directory (%default)")
31 parser.add_option("-d", "--datadir", dest="datadir",
32         help="The data dir (%default)")
33 parser.add_option("-m", "--max-entries", dest="max_entries", type="int",
34         help="Max number of entries to keep (%default)")
35
36 class Status:
37     def __init__(self):
38         self.feed_in = PyRSS2Gen.RSS2(
39                        title = "Packages entering NEW",
40                        link = "http://ftp-master.debian.org/new.html",
41                        description = "Debian packages entering the NEW queue" )
42
43         self.feed_out = PyRSS2Gen.RSS2(
44                        title = "Packages leaving NEW",
45                        link = "http://ftp-master.debian.org/new.html",
46                        description = "Debian packages leaving the NEW queue" )
47
48         self.queue = {}
49
50 def utf2ascii(src):
51     """ Return an ASCII encoded copy of the input UTF-8 string """
52     try:
53         res = unicode(src, 'utf-8').encode('ascii', 'replace')
54     except UnicodeDecodeError:
55         res = None
56     return res
57
58 def purge_old_items(feed, max):
59     """ Purge RSSItem from feed, no more than max. """
60     if feed.items is None or len(feed.items) == 0:
61         return False
62
63     feed.items = feed.items[:max]
64     return True
65
66 def parse_changes(fname):
67     """ Parse a .changes file named fname.
68
69     Return {fname: parsed} """
70
71     m = Changes(open(fname))
72
73     wanted_fields = set(['Source', 'Version', 'Architecture', 'Distribution',
74                          'Date', 'Maintainer', 'Description', 'Changes'])
75
76     if not set(m.keys()).issuperset(wanted_fields):
77         return None
78
79     return {os.path.basename(fname): m}
80
81 def parse_queuedir(dir):
82     """ Parse dir for .changes files.
83
84     Return a dictionary {filename: parsed_file}"""
85
86     if not os.path.exists(dir):
87         return None
88
89     res = {}
90     for fname in os.listdir(dir):
91         if not fname.endswith(".changes"):
92             continue
93
94         parsed = parse_changes(os.path.join(dir, fname))
95         if parsed:
96             res.update(parsed)
97
98     return res
99
100 def add_rss_item(status, msg, direction):
101     if direction == "in":
102         feed = status.feed_in
103         title = "%s %s entered NEW" % (msg['Source'], msg['Version'])
104         pubdate = msg['Date']
105     elif direction == "out":
106         feed = status.feed_out
107         title = "%s %s left NEW" % (msg['Source'], msg['Version'])
108         pubdate = datetime.utcnow()
109     else:
110         return False
111
112     description = "<pre>Description: %s\nChanges: %s\n</pre>" % \
113             (utf2ascii(msg['Description']), utf2ascii(msg['Changes']))
114
115     feed.items.insert(0,
116         PyRSS2Gen.RSSItem(
117             title,
118             pubDate = pubdate,
119             description = description,
120             author = utf2ascii(msg['Maintainer']),
121             link = "http://ftp-master.debian.org/new/%s_%s.html" % \
122                     (msg['Source'], msg['Version'])
123         )
124     )
125
126 def update_feeds(curqueue, status):
127     # inrss -> append all items in curqueue not in status.queue
128     # outrss -> append all items in status.queue not in curqueue
129
130     for (name, parsed) in curqueue.items():
131         if not status.queue.has_key(name):
132             # new package
133             add_rss_item(status, parsed, "in")
134
135     for (name, parsed) in status.queue.items():
136         if not curqueue.has_key(name):
137             # removed package
138             add_rss_item(status, parsed, "out")
139
140
141
142 if __name__ == "__main__":
143
144     (settings, args) = parser.parse_args()
145
146     if not os.path.exists(settings.outdir):
147         sys.stderr.write("Outdir '%s' does not exists\n" % settings.outdir)
148         parser.print_help()
149         sys.exit(1)
150
151     if not os.path.exists(settings.datadir):
152         sys.stderr.write("Datadir '%s' does not exists\n" % settings.datadir)
153         parser.print_help()
154         sys.exit(1)
155
156     status_db = os.path.join(settings.datadir, db_filename)
157
158     try:
159         status = cPickle.load(open(status_db))
160     except IOError:
161         status = Status()
162
163     current_queue = parse_queuedir(settings.queuedir)
164     if not current_queue:
165         sys.stderr.write("Unable to scan queuedir '%s'\n" % settings.queuedir)
166         parser.print_help()
167         sys.exit(1)
168
169     update_feeds(current_queue, status)
170
171     purge_old_items(status.feed_in, settings.max_entries)
172     purge_old_items(status.feed_out, settings.max_entries)
173
174     feed_in_file = os.path.join(settings.outdir, inrss_filename)
175     feed_out_file = os.path.join(settings.outdir, outrss_filename)
176
177     try:
178         status.feed_in.write_xml(file(feed_in_file, "w+"), "utf-8")
179         status.feed_out.write_xml(file(feed_out_file, "w+"), "utf-8")
180     except IOError, why:
181         sys.stderr.write("Unable to write feeds: %s\n", why)
182         sys.exit(1)
183
184     status.queue = current_queue
185
186     try:
187         cPickle.dump(status, open(status_db, "w+"))
188     except IOError, why:
189         sys.stderr.write("Unable to save status: %s\n", why)
190         sys.exit(1)
191
192 # vim:et:ts=4