[Tracker-discuss] List of recently modified issues
Michał Kwiatkowski
constant.beta at gmail.com
Sun Jun 10 22:22:16 CEST 2007
On 6/6/07, "Martin v. Löwis" <martin at v.loewis.de> wrote:
> I'd encourage you to check out the tracker installation, and
> propose a solution. There are two ways for report generation
> in roundup: you can either update it whenever a change is made.
> This is efficient, but also fixed with respect to the
> information available in the report. OTOH, generating the report
> dynamically allows customization, but is also more
> compute-expensive.
I went the update-on-change road, as it seemed more straightforward.
Attached to this mail is "changes_xml_writer.py" detector, which
updates recent-changes.xml file each time a file is uploaded to a
tracker. After uploading a sample attachment go to
http://localhost:9999/python-dev/_file/recent-changes.xml to see the
generated file. A single change looks like this in XML:
<change date="Sun, 10 Jun 2007 20:00:58 +0000"
id="file45-added-to-issue6" type="file-added">
<file-id>45</file-id>
<file-name>sample.patch</file-name>
<file-type>text/x-diff</file-type>
<file-url>http://localhost:9999/python-dev/file45/sample.patch</file-url>
<issue-id>6</issue-id>
</change>
Currently only file uploads are reported, although more change types
can be implemented later. File uploads are everything what I need to
continue my SoC project.
List of changes has maximum of 20 items, so the file won't grow indefinitely.
I have a question about Roundup updates though. Is a reactor function
called atomically, i.e. should I worry about two processes writing to
recent-changes.xml?
Cheers,
mk
-------------- next part --------------
import os
import urllib
from xml.dom import minidom
from time import gmtime, strftime
# Relative to tracker home directory.
FILENAME = os.path.join('%(TEMPLATES)s', 'recent-changes.xml')
def tracker_url(db):
return str(db.config.options[('tracker', 'web')])
def changes_xml_path(db):
return os.path.join(db.config.HOME, FILENAME % db.config.options)
def rfc2822_date():
return strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime())
class File(object):
def __init__(self, db, id, issue_id):
self.db = db
self.id = id
self.issue_id = issue_id
self.name = db.file.get(id, 'name')
self.type = db.file.get(id, 'type')
# Based on roundup.cgi.templating._HTMLItem.download_url().
self.download_url = tracker_url(self.db) +\
urllib.quote('%s%s/%s' % ('file', self.id, self.name))
class ChangesXml(object):
# Maximum number of changes stored in a file.
max_items = 20
def __init__(self, filename):
self.filename = filename
self._read_document()
def save(self):
self._trim_to_max_items()
fd = open(self.filename, 'w')
self.document.writexml(fd, encoding="UTF-8")
fd.close()
def add_file(self, file):
change = self._change("file%s-added-to-issue%s" % (file.id, file.issue_id),
"file-added")
change.appendChild(self._element_with_text("file-id", file.id))
change.appendChild(self._element_with_text("file-name", file.name))
change.appendChild(self._element_with_text("file-type", file.type))
change.appendChild(self._element_with_text("file-url", file.download_url))
change.appendChild(self._element_with_text("issue-id", file.issue_id))
self.root.appendChild(change)
def add_files(self, files):
for file in files:
self.add_file(file)
def _change(self, id, type):
"""Return new 'change' element of a given type.
<change id='id' date='now' type='type'></change>
"""
change = self.document.createElement("change")
change.setAttribute("id", id)
change.setAttribute("type", type)
change.setAttribute("date", rfc2822_date())
return change
def _element_with_text(self, name, value):
"""Return new element with given name and text node as a value.
<name>value</name>
"""
element = self.document.createElement(name)
text = self.document.createTextNode(str(value))
element.appendChild(text)
return element
def _trim_to_max_items(self):
"""Remove changes exceeding self.max_items.
"""
# Assumes that changes are stored sequentially from oldest to newest.
# Will do for now.
for change in self.root.getElementsByTagName("change")[0:-self.max_items]:
self.root.removeChild(change)
def _read_document(self):
try:
self.document = minidom.parse(self.filename)
self.root = self.document.firstChild
except IOError, e:
if e.errno != 2:
raise
self._create_new_document()
def _create_new_document(self):
self.document = minidom.Document()
self.root = self.document.createElement("changes")
self.document.appendChild(self.root)
def get_new_files_ids(issue_now, issue_then):
"""Return ids of files added between `now` and `then`.
"""
files_now = set(issue_now['files'])
files_then = set(issue_then['files']) if issue_then else set()
return map(int, files_now - files_then)
def file_added_to_issue(db, cl, issue_id, olddata):
changes = ChangesXml(changes_xml_path(db))
issue = db.issue.getnode(issue_id)
new_files = [ File(db, id, issue_id) for id in get_new_files_ids(issue, olddata) ]
changes.add_files(new_files)
changes.save()
def init(db):
db.issue.react('create', file_added_to_issue)
db.issue.react('set', file_added_to_issue)
More information about the Tracker-discuss
mailing list