#!/usr/bin/python -u

"""bzr-feed.py

An Atom feed generator for a Bazaar repository.
"""

__author__ = "Morten Frederiksen (morten@mfd-consult.dk)"
__copyright__ = "copyright 2007-2010, MFD Consult"
__contributors__ = ["Sam Ruby", "Jacques Distler", "Jason Blevins"]
__license__ = "Python"

from bzrlib.branch import BzrBranch
from bzrlib.bzrdir import BzrDir
from bzrlib.log import show_log, LogFormatter
from xml.sax import saxutils
import time, os, cgi, sys, re, hashlib

bzr_rev = re.compile("(.*?)-(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)-(.*)")

class AtomFeed(LogFormatter):
    """Print log messages as an Atom feed.
    """
    def __init__(self, baseuri, branch, created, updated):
        super(AtomFeed, self).__init__(to_file=None)
        self.supports_delta = True
        self.baseuri = baseuri
        if self.baseuri != "":
            self.baseuri = "http://" + self.baseuri
        self.branch = branch
        self.feedid = "tag:" + baseuri.replace('/', "," + time.strftime("%Y-%m-%d",time.gmtime(created)) + ":", 1)

        if os.environ.get('SCRIPT_NAME',None):
            print "Content-Type: application/atom+xml\r\n\r\n",
        print "<?xml version='1.0' encoding='utf-8'?>"
        print "<feed xmlns='http://www.w3.org/2005/Atom'>"
        print "  <title>" + self._e(self.branch.nick) + "</title>"
        print "  <id>" + self._e(self.feedid) + "</id>"
        print "  <link href='" + self._e(self.baseuri) + "/'/>"
        print "  <link rel='self' type='application/atom+xml' href='"  + os.environ.get('REQUEST_URI', '') + "'/>"
        print "  <updated>" + self._e(time.strftime("%Y-%m-%dT%H:%M:%SZ",time.gmtime(updated))) + "</updated>"

    def __del__(self):
        print "</feed>"
    
    def _e(self, msg):
        return saxutils.escape(msg.encode('utf-8'))

    def show_files(self, files, label):
        if not len(files): return
        print "      <dt>" + self._e(label) + ":</dt><dd><ul>"
        for item in files:
             if label == 'Renamed':
                 display_path, path, fid, kind = item[:4]
             else:
                 path, fid, kind = item[:3]
                 display_path = path

             if kind == 'directory':
                 path += '/'
                 display_path += '/'
             elif kind == 'symlink':
                 display_path += '@'
             if len(item) == 5 and item[4]:
                 display_path += '*'

             if label == 'Removed':
                 print "        <li>" + self._e(display_path) + "</li>"
             else:
                 print "        <li><a href='%s/%s'>%s</a></li>" % (self.baseuri, self._e(path), self._e(display_path))

        print "      </ul></dd>"

    def log_revision(self, revision):
        revno = revision.revno
        rev = revision.rev
        delta = revision.delta
        revno = str(revno)
        message = rev.message.rstrip().split('\n')

        print
        print "  <entry>"
        print "    <author>"
        print "      <name>" + self._e(rev.committer.split("<")[0].rstrip()) + "</name>"
        print "    </author>"
        print "    <title>Revision " + self._e(revno) + ": " + self._e(message[0]) + "</title>"
        print "    <link href='" + self._e(self.baseuri + "/#" + revno) + "'/>"
        if bzr_rev.match(rev.revision_id):
            print "    <id>" + self._e(bzr_rev.sub(r"tag:\1,\2-\3-\4:\5:\6:\7-\8",rev.revision_id)) + "</id>"
        else:
            print "    <id>" + self._e(self.feedid + ":" + revno) + "</id>"
        print "    <updated>" + self._e(time.strftime("%Y-%m-%dT%H:%M:%SZ",time.gmtime(rev.timestamp))) + "</updated>"
        print "    <content type='xhtml'><div xmlns='http://www.w3.org/1999/xhtml'>"
        if message[1:]:
            print "    <p>" + "<br/>\n".join(map(self._e, message[1:])) + "</p>"
        if delta:
            print "    <dl>"
            self.show_files(delta.added, "Added")
            self.show_files(delta.removed, "Removed")
            self.show_files(delta.renamed, "Renamed")
            self.show_files(delta.modified, "Modified")
            print "    </dl>"
        print "    </div></content>"
        print "  </entry>"

def status(code, msg):
    if (code!=304): print "Content-Type: text/plain"
    print "Status: " + str(code) + " " + msg
    print
    if (code!=304): print msg
    sys.exit()

# Sanity check
if not os.environ.get('REQUEST_METHOD','GET') in ['GET', 'HEAD']:
    status(405, "Method Not Allowed")

# Basic repository info
try:
    dir = cgi.FieldStorage().getvalue("dir")
    repo = BzrDir.open(dir).open_repository()
    branch_dir = dir
    if cgi.FieldStorage().has_key("branch"):
        branch_dir += '/' + cgi.FieldStorage().getvalue("branch")
except:
    status(404, "Repository Not Found")
if not len(repo.all_revision_ids()):
    status(404, "No Revisions Found")

# Basic branch info
branch = BzrDir.open(branch_dir).open_branch()
start_rev = branch.revno()-9
if (start_rev < 1): start_rev = 1
first_rev = repo.get_revision(branch.get_rev_id(1))
last_rev = repo.get_revision(branch.get_rev_id(branch.revno()))

# Support 304
if_none_match = os.environ.get('HTTP_IF_NONE_MATCH', '')
if_modified_since = os.environ.get('HTTP_IF_MODIFIED_SINCE', '')
if if_none_match and ('"' + str(hashlib.md5(branch.nick + str(last_rev.timestamp)).hexdigest()) + '"') == if_none_match:
    status(304, "Not Modified")

# Location
baseuri = os.environ.get('HTTP_HOST', '')
if os.environ.get('SERVER_PORT', '80')!="80":
    baseuri += ":" + os.environ.get('SERVER_PORT', '80')
if os.path.basename(os.path.dirname(os.environ.get('REQUEST_URI', ''))) == dir:
    baseuri += os.path.dirname(os.environ.get('REQUEST_URI', ''))
else:
    baseuri += re.sub(r'\.\w+$', '', os.environ.get('REQUEST_URI', ''))

if os.environ.get('SCRIPT_NAME',None):
    print "Last-Modified: " + time.ctime(last_rev.timestamp + time.timezone) + " GMT"
    print 'ETag: "%s"' % str(hashlib.md5(branch.nick + str(last_rev.timestamp)).hexdigest())

lf = AtomFeed(baseuri, branch, first_rev.timestamp, last_rev.timestamp)
show_log(branch,lf,None,True,'reverse',start_rev,branch.revno())
