123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- #! /usr/bin/env python3
- # Generate two rss feeds for a directory with .changes file
- # License: GPL v2 or later
- # Author: Filippo Giunchedi <filippo@debian.org>
- # Version: 0.5
- import html
- import os
- import os.path
- import pickle
- import re
- import sys
- import time
- from optparse import OptionParser
- from datetime import datetime
- from email.utils import parseaddr
- import PyRSS2Gen
- from debian.deb822 import Changes
- inrss_filename = "NEW_in.rss"
- outrss_filename = "NEW_out.rss"
- db_filename = "status.db"
- parser = OptionParser()
- parser.set_defaults(queuedir="queue", outdir="out", datadir="status",
- logdir="log", max_entries="30")
- parser.add_option("-q", "--queuedir", dest="queuedir",
- help="The queue dir (%default)")
- parser.add_option("-o", "--outdir", dest="outdir",
- help="The output directory (%default)")
- parser.add_option("-d", "--datadir", dest="datadir",
- help="The data dir (%default)")
- parser.add_option("-l", "--logdir", dest="logdir",
- help="The ACCEPT/REJECT dak log dir (%default)")
- parser.add_option("-m", "--max-entries", dest="max_entries", type="int",
- help="Max number of entries to keep (%default)")
- class Status:
- def __init__(self):
- self.feed_in = PyRSS2Gen.RSS2(
- title="Packages entering NEW",
- link="https://ftp-master.debian.org/new.html",
- description="Debian packages entering the NEW queue")
- self.feed_out = PyRSS2Gen.RSS2(
- title="Packages leaving NEW",
- link="https://ftp-master.debian.org/new.html",
- description="Debian packages leaving the NEW queue")
- self.queue = {}
- def purge_old_items(feed, max):
- """ Purge RSSItem from feed, no more than max. """
- if feed.items is None or len(feed.items) == 0:
- return False
- feed.items = feed.items[:max]
- return True
- def parse_changes(fname):
- """ Parse a .changes file named fname.
- Return {fname: parsed} """
- m = Changes(open(fname))
- wanted_fields = set(['Source', 'Version', 'Architecture', 'Distribution',
- 'Date', 'Changed-By', 'Description', 'Changes'])
- if not set(m.keys()).issuperset(wanted_fields):
- return None
- return {os.path.basename(fname): m}
- def parse_queuedir(dir):
- """ Parse dir for .changes files.
- Return a dictionary {filename: parsed_file}"""
- if not os.path.exists(dir):
- return None
- res = {}
- for fname in os.listdir(dir):
- if not fname.endswith(".changes"):
- continue
- parsed = parse_changes(os.path.join(dir, fname))
- if parsed:
- res.update(parsed)
- return res
- def parse_leave_reason(fname):
- """ Parse a dak log file fname for ACCEPT/REJECT reason from process-new.
- Return a dictionary {filename: reason}"""
- reason_re = re.compile(r".+\|process-new\|(.+)\|NEW (ACCEPT|REJECT)\|(\S+)")
- try:
- f = open(fname)
- except OSError as e:
- print("Can't open %s: %s" % (fname, e), file=sys.stderr)
- return {}
- res = {}
- for l in f.readlines():
- m = reason_re.search(l)
- if m:
- res[m.group(3)] = (m.group(2), m.group(1))
- f.close()
- return res
- def add_rss_item(status, msg, direction):
- if direction == "in":
- feed = status.feed_in
- title = "%s %s entered NEW" % (msg['Source'], msg['Version'])
- pubdate = msg['Date']
- elif direction == "out":
- feed = status.feed_out
- if 'Leave-Reason' in msg:
- title = "%s %s left NEW (%s)" % (msg['Source'], msg['Version'],
- msg['Leave-Reason'][0])
- else:
- title = "%s %s left NEW" % (msg['Source'], msg['Version'])
- pubdate = datetime.utcnow()
- else:
- return False
- description = "<pre>Description: %s\nChanges: %s\n</pre>" % \
- (html.escape(msg['Description']),
- html.escape(msg['Changes']))
- link = "https://ftp-master.debian.org/new/%s_%s.html" % \
- (msg['Source'], msg['Version'])
- guid = msg['Checksums-Sha256'][0]['sha256']
- if 'Processed-By' in msg:
- author = msg['Processed-By']
- else:
- changedby = parseaddr(msg['Changed-By'])
- author = "%s (%s)" % (changedby[1], changedby[0])
- feed.items.insert(0,
- PyRSS2Gen.RSSItem(
- title,
- pubDate=pubdate,
- description=description,
- author=html.escape(author),
- link=link,
- guid=guid
- )
- )
- def update_feeds(curqueue, status, settings):
- # inrss -> append all items in curqueue not in status.queue
- # outrss -> append all items in status.queue not in curqueue
- leave_reason = None
- # logfile from dak's process-new
- reason_log = os.path.join(settings.logdir, time.strftime("%Y-%m"))
- for (name, parsed) in curqueue.items():
- if name not in status.queue:
- # new package
- add_rss_item(status, parsed, "in")
- for (name, parsed) in status.queue.items():
- if name not in curqueue:
- # removed package, try to find out why
- if leave_reason is None:
- leave_reason = parse_leave_reason(reason_log)
- if leave_reason and name in leave_reason:
- parsed['Leave-Reason'] = leave_reason[name][0]
- parsed['Processed-By'] = leave_reason[name][1] + "@debian.org"
- add_rss_item(status, parsed, "out")
- if __name__ == "__main__":
- (settings, args) = parser.parse_args()
- if not os.path.exists(settings.outdir):
- print("Outdir '%s' does not exists" % settings.outdir, file=sys.stderr)
- parser.print_help()
- sys.exit(1)
- if not os.path.exists(settings.datadir):
- print("Datadir '%s' does not exists" % settings.datadir, file=sys.stderr)
- parser.print_help()
- sys.exit(1)
- status_db = os.path.join(settings.datadir, db_filename)
- try:
- with open(status_db, 'rb') as fh:
- try:
- status = pickle.load(fh, encoding="utf-8")
- except UnicodeDecodeError:
- fh.seek(0)
- status = pickle.load(fh, encoding="latin-1")
- except OSError:
- status = Status()
- current_queue = parse_queuedir(settings.queuedir)
- update_feeds(current_queue, status, settings)
- purge_old_items(status.feed_in, settings.max_entries)
- purge_old_items(status.feed_out, settings.max_entries)
- feed_in_file = os.path.join(settings.outdir, inrss_filename)
- feed_out_file = os.path.join(settings.outdir, outrss_filename)
- try:
- status.feed_in.write_xml(open(feed_in_file, "w+"), "utf-8")
- status.feed_out.write_xml(open(feed_out_file, "w+"), "utf-8")
- except OSError as why:
- print("Unable to write feeds:", why, file=sys.stderr)
- sys.exit(1)
- status.queue = current_queue
- try:
- with open(status_db, 'wb+') as fh:
- pickle.dump(status, fh)
- except OSError as why:
- print("Unable to save status:", why, file=sys.stderr)
- sys.exit(1)
- # vim:et:ts=4
|