[Python-checkins] r56616 - tracker/instances/python-dev/detectors/changes_xml_writer.py
erik.forsberg
python-checkins at python.org
Sun Jul 29 13:55:54 CEST 2007
Author: erik.forsberg
Date: Sun Jul 29 13:55:54 2007
New Revision: 56616
Added:
tracker/instances/python-dev/detectors/changes_xml_writer.py
Log:
Adding recent-changes detector in python 2.4 version. Resolves http://psf.upfronthosting.co.za/roundup/meta/file55/changes_xml_writer-2.4.py
Added: tracker/instances/python-dev/detectors/changes_xml_writer.py
==============================================================================
--- (empty file)
+++ tracker/instances/python-dev/detectors/changes_xml_writer.py Sun Jul 29 13:55:54 2007
@@ -0,0 +1,194 @@
+#
+# changes.xml writer detector.
+#
+# Copyright (c) 2007 Michal Kwiatkowski <constant.beta at gmail.com>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of the author nor the names of his contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+"""changes.xml writer detector -- save each database change to an XML file.
+
+Root element is called `changes` and it has at most `ChangesXml.max_items`
+children, each called a `change`. Each `change` has the following attributes:
+
+:date: date in RFC2822 format when the change was made
+:id: unique identifier of this change (note: not an integer)
+:type: type of this change (see below)
+
+A structure of a `change` depends on its `type`. Currently implemented
+change types and their formats are listed below.
+
+* type = `file-added`
+
+ Describes a new file attached to an existing issue. Child elements:
+
+ :file-id: unique integer identifier of the file
+ :file-name: name of the uploaded file
+ :file-type: MIME type of the file content
+ :file-url: permanent URL of the file
+ :issue-id: unique integer identifier of an issue this file is attached to
+"""
+
+import os
+import urllib
+from xml.dom import minidom
+from xml.parsers.expat import ExpatError
+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()
+ self.modified = False
+
+ def save(self):
+ if not self.modified:
+ return
+
+ 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)
+ self.modified = True
+
+ 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:
+ # File not found, create a new one then.
+ if e.errno != 2:
+ raise
+ self._create_new_document()
+ except ExpatError:
+ # File has been damaged, forget about it and create a new one.
+ 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'])
+ if issue_then:
+ files_then = set(issue_then['files'])
+ else:
+ files_then = set()
+ return map(int, files_now - files_then)
+
+def file_added_to_issue(db, cl, issue_id, olddata):
+ try:
+ 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()
+ except:
+ # We can't mess up with a database commit.
+ pass
+
+
+def init(db):
+ db.issue.react('create', file_added_to_issue)
+ db.issue.react('set', file_added_to_issue)
More information about the Python-checkins
mailing list