Below is the file 'handlers.py' from this revision. You can also download the file.
# Copyright (C) 2005 Grahame Bowland <grahame@angrygoats.net> # # This program is made available under the GNU GPL version 2.0 or # greater. See the accompanying file COPYING for details. # # This program is distributed WITHOUT ANY WARRANTY; without even the # implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR # PURPOSE. # Python standard modules import os, cgi, sys, struct, rfc822, urllib import tarfile, tempfile, datetime, cStringIO, heapq, binascii # web.py import web # JSON utility module import json # FreeDesktop.org share mime info and icon theme specification from fdo import sharedmimeinfo, icontheme # Other bits of ViewMTN import mtn, common, syntax, release, branchdiv from ancestry import ancestry_graph from render import * hq = cgi.escape # The user configuration file import config # MIME lookup instance mimehelp = sharedmimeinfo.LookupHelper(getattr(config, "mime_map", None)) # Icon theme access if config.icon_theme: try: mimeicon = icontheme.MimeIcon(icontheme.IconTheme(config.icon_theme), config.icon_size) except Exception, e: print>>sys.stderr, "\nFailed to load icon theme: perhaps you want to set config.icon_theme = None\n" raise e else: mimeicon = None class Index(object): def GET(self, ctxt): branches = list(ctxt.ops.branches ()) ctxt.branchdivs.calculate_divisions (branches) def division_iter(): bitter = iter(branches) divs = ctxt.branchdivs.divisions n_divs = len(divs) in_divs = {} look_for = 0 def new_div (n): did = look_for in_divs[n] = did return "d", did, mtn.Branch(n), len(in_divs.keys ()) * 10 def end_div (n): did = in_divs.pop (n) return "e", did, mtn.Branch(n), len(in_divs.keys ()) * 10 def branch_line (b): return "b", 0, branch, 0 for branch in bitter: for div in in_divs.keys(): # we alter it in the loop, copy.. if branch.name.find (div) != 0: yield end_div (div) if look_for < n_divs: if cmp(branch, divs[look_for]) > 0: look_for += 1 if branch.name.find (divs[look_for]) == 0: yield new_div (divs[look_for]) look_for += 1 yield branch_line (branch) # any stragglers need to be closed for div in in_divs.keys(): yield end_div (div) ctxt.render('index.html', page_title="Branches", branches=division_iter()) class About(object): def GET(self, ctxt): ctxt.render('about.html', page_title="About") class Tags(object): def GET(self, ctxt, restrict_branch=None): # otherwise we couldn't use automate again.. tags = list(ctxt.ops.tags()) if restrict_branch != None: restrict_branch = mtn.Branch(restrict_branch) def tag_in(tag): for branch in tag.branches: if branch.name == restrict_branch.name: return tag return None tags = filter(tag_in, tags) template_file = "branchtags.html" else: template_file = "tags.html" tags.sort(lambda t1, t2: cmp(t1.name, t2.name)) def revision_ago(rev): rv = "" for cert in ctxt.ops.certs(rev): if cert[4] == 'name' and cert[5] == 'date': revdate = common.parse_timecert(cert[7]) rv = common.ago(revdate) return rv ctxt.render(template_file, page_title="Tags", tags=tags, revision_ago=revision_ago, branch=restrict_branch) class Help(object): def GET(self, ctxt): ctxt.render('help.html', page_title="Help") class Changes(object): class ComparisonRev: def __init__(self, ops, revision): self.revision = revision self.certs = list(ops.certs(self.revision)) self.date = None for cert in self.certs: if cert[4] == 'name' and cert[5] == 'date': self.date = common.parse_timecert(cert[7]) def __repr__(self): return "ComparisonRev <%s>" % (repr(self.revision)) def __cmp__(self, other): # irritating edge-case, heapq compares us to empty string if # there's only one thing in the list if not other: return 1 return cmp(other.date, self.date) def __get_last_changes(self, ops, start_from, parent_func, selection_func, n): """returns at least n revisions that are parents of the revisions in start_from, ordered by time (descending). selection_func is called for each revision, and that revision is only included in the result if the function returns True.""" # revised algorithm in colaboration with Matthias Radestock # # use a heapq to keep a list of interesting revisions # pop from the heapq; get the most recent interesting revision # insert the parents of this revision into the heap # .. repeat until len(heap) >= to_count if not start_from: raise Exception("get_last_changes() unable to find somewhere to start.") last_result = None in_result = set() result = [] revq = [] for rev in start_from: heapq.heappush(revq, Changes.ComparisonRev(ops, rev)) while len(result) < n: # print >>sys.stderr, "start_revq state:", map(lambda x: (x.revision, x.date), revq) # update based on the last result we output if last_result != None: parents = filter(None, parent_func(last_result.revision)) for parent_rev in parents: if parent_rev == None: continue heapq.heappush(revq, Changes.ComparisonRev(ops, parent_rev)) # try and find something we haven't already output in the heap last_result = None while revq: candidate = heapq.heappop(revq) if not (candidate.revision in in_result) and selection_func(candidate.revision): last_result = candidate break if last_result == None: break # follow the newest edge in_result.add(last_result.revision) result.append(last_result) rv = map (lambda x: (x.revision, x.certs), result), revq return rv def on_our_branch(self, ops, branch, revision): rv = False for cert in ops.certs(revision): if cert[4] == 'name' and cert[5] == 'branch': if cert[7] == branch.name: rv = True return rv def for_template(self, ctxt, revs, pathinfo=None, constrain_diff_to=None): rv = [] for rev, certs in revs: rev_branch = "" revision, diffs, ago, author, changelog, shortlog, when = mtn.Revision(rev), [], "", mtn.Author(""), "", "", "" for cert in certs: if cert[4] != 'name': continue if cert[5] == "branch": rev_branch = cert[7] elif cert[5] == 'date': revdate = common.parse_timecert(cert[7]) ago = common.ago(revdate) when = revdate.strftime('%a, %d %b %Y %H:%M:%S GMT') elif cert[5] == 'author': author = mtn.Author(cert[7]) elif cert[5] == 'changelog': changelog = normalise_changelog(cert[7]) # NB: this HTML escapes! shortlog = quicklog(changelog) # so this is also HTML escaped. if pathinfo != None: filename = pathinfo.get(rev) else: filename = None if constrain_diff_to: diff_to_revision = mtn.Revision(constrain_diff_to) else: diff_to_revision = revision for stanza in ctxt.ops.get_revision(rev): if stanza and stanza[0] == "old_revision": diffs.append(Diff(mtn.Revision(stanza[1]), diff_to_revision, filename)) if diffs: if constrain_diff_to: diffs = [ diffs[0] ] diffs = '| ' + ', '.join([ctxt.link(d).html('diff') for d in diffs]) else: diffs = '' rv.append((revision, diffs, ago, mtn.Author(author), '<br />\n'.join(changelog), shortlog, when, mtn.File(filename, rev))) return rv def determine_bounds(self, from_change, to_change): per_page = 10 max_per_page = 100 if from_change: from_change = int(from_change) else: from_change = 0 if to_change: to_change = int(to_change) else: to_change = per_page if to_change - from_change > max_per_page: to_change = from_change + max_per_page next_from = to_change next_to = to_change + per_page previous_from = from_change - per_page previous_to = previous_from + per_page return (from_change, to_change, next_from, next_to, previous_from, previous_to) def branch_get_last_changes(self, ops, branch, from_change, to_change): heads = [t for t in ops.heads(branch.name)] if not heads: return web.notfound() changed, new_starting_point = self.__get_last_changes(ops, heads, lambda r: ops.parents(r), lambda r: self.on_our_branch(ops, branch, r), to_change) return changed, new_starting_point def Branch_GET(self, ctxt, branch, from_change, to_change, template_name): branch = mtn.Branch(branch) from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change) last_changes = self.branch_get_last_changes(ctxt.ops, branch, from_change, to_change) if last_changes is None: return web.notfound() changed, new_starting_point = last_changes changed = changed[from_change:to_change] if len(changed) != to_change - from_change: next_from, next_to = None, None if from_change <= 0: previous_from, previous_to = None, None ctxt.render(template_name, page_title="Branch %s" % branch.name, branch=branch, from_change=from_change, to_change=to_change, previous_from=previous_from, previous_to=previous_to, next_from=next_from, next_to=next_to, display_revs=self.for_template(ctxt, changed)) def file_get_last_changes(self, ops, from_change, to_change, revision, path): def content_changed_fn(start_revision, start_path, in_revision, pathinfo): uniq = set() parents = list(ops.parents(in_revision)) for parent in parents: stanza = list(ops.get_corresponding_path(start_revision, start_path, parent)) # file does not exist in this revision; skip! if not stanza: continue # follow the white rabbit current_path = stanza[0][1] stanza = list(ops.get_content_changed(parent, current_path)) to_add = stanza[0][1] uniq.add(to_add) pathinfo[to_add] = current_path return list(uniq) pathinfo = {} # not just the starting revision! we might not have changed 'path' in the starting rev.. start_at = content_changed_fn(revision, path, revision, pathinfo) changed, new_starting_point = self.__get_last_changes(ops, start_at, lambda r: content_changed_fn(revision, path, r, pathinfo), lambda r: True, to_change) return changed, new_starting_point, pathinfo def File_GET(self, ctxt, from_change, to_change, revision, path, template_name): from_change, to_change, next_from, next_to, previous_from, previous_to = self.determine_bounds(from_change, to_change) changed, new_starting_point, pathinfo = self.file_get_last_changes(ctxt.ops, from_change, to_change, revision, path) changed = changed[from_change:to_change] if len(changed) != to_change - from_change: next_from, next_to = None, None if from_change <= 0: previous_from, previous_to = None, None ctxt.render(template_name, page_title="Changes to '%s' (from %s)" % (cgi.escape(path), revision.abbrev()), filename=mtn.File(path, revision), revision=revision, from_change=from_change, to_change=to_change, previous_from=previous_from, previous_to=previous_to, next_from=next_from, next_to=next_to, display_revs=self.for_template(ctxt, changed, pathinfo=pathinfo, constrain_diff_to=revision)) class HTMLBranchChanges(Changes): def GET(self, ctxt, branch, from_change, to_change): Changes.Branch_GET(self, ctxt, branch, from_change, to_change, "branchchanges.html") class RSSBranchChanges(Changes): def GET(self, ctxt, branch, from_change, to_change): Changes.Branch_GET(self, ctxt, branch, from_change, to_change, "branchchangesrss.html") class RevisionPage(object): def get_fileid(self, ops, revision, filename): rv = None for stanza in ops.get_manifest_of(revision): if stanza[0] != 'file': continue if stanza[1] == filename: rv = stanza[3] return rv def exists(self, ops, revision): try: certs = [t for t in ops.certs(revision)] return True except mtn.MonotoneException: return False def branches_for_rev(self, ops, revisions_val): rv = [] for stanza in ops.certs(revisions_val): if stanza[4] == 'name' and stanza[5] == 'branch': rv.append(stanza[7]) return rv class RevisionFileChanges(Changes, RevisionPage): def GET(self, ctxt, from_change, to_change, revision, path): revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() Changes.File_GET(self, ctxt, from_change, to_change, revision, path, "revisionfilechanges.html") class RevisionFileChangesRSS(Changes, RevisionPage): def GET(self, ctxt, from_change, to_change, revision, path): revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() Changes.File_GET(self, ctxt, from_change, to_change, revision, path, "revisionfilechangesrss.html") class RevisionInfo(RevisionPage): def GET(self, ctxt, revision): revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() certs = ctxt.ops.certs(revision) revisions = ctxt.ops.get_revision(revision) output_png, output_imagemap = ancestry_graph(ctxt, revision) if os.access(output_imagemap, os.R_OK): imagemap = open(output_imagemap).read().replace('\\n', ' by ') imageuri = ctxt.perdb_join('revision/graph/' + revision) else: imagemap = imageuri = None ctxt.render('revisioninfo.html', page_title="Revision %s" % revision.abbrev(), revision=revision, certs=certs_for_template(ctxt, certs), imagemap=imagemap, imageuri=imageuri, revisions=revisions_for_template(ctxt, revision, revisions)) class RevisionDiff(RevisionPage): def GET(self, ctxt, revision_from, revision_to, filename=None): revision_from = mtn.Revision(revision_from) revision_to = mtn.Revision(revision_to) if not self.exists(ctxt.ops, revision_from): return web.notfound() if not self.exists(ctxt.ops, revision_to): return web.notfound() if filename != None: files = [filename] else: files = [] diff = ctxt.ops.diff(revision_from, revision_to, files) diff_obj = Diff(revision_from, revision_to, files) ctxt.render('revisiondiff.html', page_title="Diff from %s to %s" % (revision_from.abbrev(), revision_to.abbrev()), revision=revision_from, revision_from=revision_from, revision_to=revision_to, diff=syntax.highlight(diff, 'diff'), diff_obj=diff_obj, files=files) class RevisionRawDiff(RevisionPage): def GET(self, ctxt, revision_from, revision_to, filename=None): revision_from = mtn.Revision(revision_from) revision_to = mtn.Revision(revision_to) if not self.exists(ctxt.ops, revision_from): return web.notfound() if not self.exists(ctxt.ops, revision_to): return web.notfound() if filename != None: files = [filename] else: files = [] diff = ctxt.ops.diff(revision_from, revision_to, files) web.header('Content-Type', 'text/x-diff') for line in diff: sys.stdout.write (line) sys.stdout.flush() class RevisionFile(RevisionPage): def GET(self, ctxt, revision, filename): revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() language = filename.rsplit('.', 1)[-1] fileid = RevisionPage.get_fileid(self, ctxt.ops, revision, filename) if not fileid: return web.notfound() contents = ctxt.ops.get_file(fileid) mimetype = mimehelp.lookup(filename, '') mime_to_template = { 'image/jpeg' : 'revisionfileimg.html', 'image/png' : 'revisionfileimg.html', 'image/gif' : 'revisionfileimg.html', 'image/svg+xml' : 'revisionfileobj.html', 'application/pdf' : 'revisionfileobj.html', 'application/x-python' : 'revisionfiletxt.html', 'application/x-perl' : 'revisionfiletxt.html', } template = mime_to_template.get(mimetype, None) if not template: if mimetype.startswith('text/'): template = 'revisionfiletxt.html' else: template = 'revisionfilebin.html' ctxt.render(template, filename=mtn.File(filename, revision), page_title="File %s in revision %s" % (filename, revision.abbrev()), revision=revision, mimetype=mimetype, contents=syntax.highlight(contents, language)) class RevisionDownloadFile(RevisionPage): def GET(self, ctxt, revision, filename): web.header('Content-Disposition', 'attachment; filename=%s' % filename) revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() fileid = RevisionPage.get_fileid(self, ctxt.ops, revision, filename) if not fileid: return web.notfound() for idx, data in enumerate(ctxt.ops.get_file(fileid)): if idx == 0: mimetype = mimehelp.lookup(filename, data) web.header('Content-Type', mimetype) sys.stdout.write(data) sys.stdout.flush() class RevisionTar(RevisionPage): def GET(self, ctxt, revision): # we'll output in the USTAR tar format; documentation taken from: # http://en.wikipedia.org/wiki/Tar_%28file_format%29 revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() filename = "%s.tar" % revision web.header('Content-Disposition', 'attachment; filename=%s' % filename) web.header('Content-Type', 'application/x-tar') manifest = [stanza for stanza in ctxt.ops.get_manifest_of(revision)] # for now; we might want to come up with something more interesting; # maybe the branch name (but there might be multiple branches?) basedirname = revision tarobj = tarfile.open(name=filename, mode="w", fileobj=sys.stdout) dir_mode, file_mode = "0700", "0600" certs = {} for stanza in manifest: stanza_type = stanza[0] if stanza_type != 'file': continue filename, fileid = stanza[1], stanza[3] filecontents = cStringIO.StringIO() filesize = 0 for data in ctxt.ops.get_file(fileid): filesize += len(data) filecontents.write(data) ti = tarfile.TarInfo() ti.name = os.path.join(revision, filename) ti.mode, ti.type = 00600, tarfile.REGTYPE ti.uid = ti.gid = 0 # determine the most recent of the content marks content_marks = [t[1] for t in ctxt.ops.get_content_changed(revision, filename)] if len(content_marks) > 0: # just pick one to make this faster content_mark = content_marks[0] since_epoch = timecert(ctxt.ops.certs(content_mark)) - datetime.datetime.fromtimestamp(0) ti.mtime = since_epoch.days * 24 * 60 * 60 + since_epoch.seconds else: ti.mtime = 0 ti.size = filesize filecontents.seek(0) tarobj.addfile(ti, filecontents) class RevisionBrowse(RevisionPage): def GET(self, ctxt, revision, path): revision = mtn.Revision(revision) if not self.exists(ctxt.ops, revision): return web.notfound() branches = RevisionPage.branches_for_rev(self, ctxt.ops, revision) revisions = ctxt.ops.get_revision(revision) def components(path): # NB: mtn internally uses '/' for paths, so we shouldn't use os.path.join() # we should do things manually; otherwise we'll break on other platforms # when we accidentally use \ or : or whatever. # # also, let's handle the case of spurious extra / characters # whatever we return should make sense as '/'.join(rv) rv = [] while path: path = path.lstrip('/') pc = path.split('/', 1) if len(pc) == 2: rv.append(pc[0]) path = pc[1] else: rv.append(pc[0]) path = '' return rv path = path or "" path_components = components(path) normalised_path = '/'.join(path_components) # TODO: detect whether or not this exists and skip the following if it doesn't. page_title = "Browsing revision %s: dir %s/" % (revision.abbrev(), normalised_path or '') if len(branches) > 0: if len(branches) == 1: branch_plural = 'branch' else: branch_plural = 'branches' page_title += " of %s %s" % (branch_plural, ', '.join(branches)) def cut_manifest_to_subdir(): manifest = list(ctxt.ops.get_manifest_of(revision)) in_the_dir = False for stanza in manifest: stanza_type = stanza[0] if stanza_type != "file" and stanza_type != "dir": continue this_path = stanza[1] if not in_the_dir: if stanza_type == "dir" and this_path == normalised_path: in_the_dir = True continue this_path_components = components(this_path) # debug(["inthedir", stanza_type, this_path, len(this_path_components), len(path_components)]) if stanza_type == "dir": # are we still in our directory? if len(this_path_components) > len(path_components) and \ this_path_components[:len(path_components)] == path_components: # is this an immediate subdirectory of our directory? if len(this_path_components) == len(path_components) + 1: yield (stanza_type, this_path) else: in_the_dir = False # and we've come out of the dir ne'er to re-enter, so.. break elif stanza_type == "file" and len(this_path_components) == len(path_components) + 1: yield (stanza_type, this_path) def info_for_manifest(entry_iter): # should probably limit memory usage (worst case is this gets huge) # but for now, this is really a needed optimisation, as most of the # time a single cert will be seen *many* times certs = {} certinfo = {} def get_cert(revision): if not certs.has_key(revision): # subtle bug slipped in here; ops.cert() is a generator # so we can't just store it in a cache! certs[revision] = list(ctxt.ops.certs(revision)) return certs[revision] def _get_certinfo(revision): author, ago, shortlog = None, None, None for cert in get_cert(revision): if cert[4] != 'name': continue name, value = cert[5], cert[7] if name == "author": author = mtn.Author(value) elif name == "date": revdate = common.parse_timecert(value) ago = common.ago(revdate) elif name == "changelog": shortlog = quicklog(normalise_changelog(value), 40) to_return = (author, ago, shortlog) return [t or "" for t in to_return] def get_certinfo(revision): if not certinfo.has_key(revision): certinfo[revision] = _get_certinfo(revision) return certinfo[revision] for stanza_type, this_path in entry_iter: # determine the most recent of the content marks content_marks = [t[1] for t in ctxt.ops.get_content_changed(revision, this_path)] for mark in content_marks: get_cert(mark) if len(content_marks): content_marks.sort(lambda b, a: cmp(timecert(certs[a]), timecert(certs[b]))) content_mark = mtn.Revision(content_marks[0]) author, ago, shortlog = get_certinfo(content_mark) else: author, ago, shortlog, content_mark = mtn.Author(""), "", "", None if stanza_type == "file": file_obj = mtn.File(this_path, revision) mime_type = mimehelp.lookup(this_path, "") else: file_obj = mtn.Dir(this_path, revision) mime_type = 'inode/directory' yield (stanza_type, file_obj, author, ago, content_mark, shortlog, mime_type) def path_links(components): # we always want a link to '/' yield mtn.Dir('/', revision) running_path = "" for component in components: running_path += component + "/" yield mtn.Dir(running_path, revision) def row_class(): while True: yield "odd" yield "even" def mime_icon(mime_type): return ctxt.nodb_join('mimeicon/' + mime_type) ctxt.render('revisionbrowse.html', branches=branches, branch_links=', '.join([ctxt.link(mtn.Branch(b)).html() for b in branches]), path=path, page_title=page_title, revision=revision, path_links=path_links(path_components), row_class=row_class(), mime_icon=mime_icon, entries=info_for_manifest(cut_manifest_to_subdir())) class RevisionGraph(object): def GET(self, ctxt, revision): output_png, output_imagemap = ancestry_graph(ctxt, revision) if os.access(output_png, os.R_OK): web.header('Content-Type', 'image/png') sys.stdout.write(open(output_png).read()) else: return web.notfound() class Json(object): def fill_from_certs(self, rv, certs): for cert in certs: if cert[4] != 'name': continue if cert[5] == 'author': rv['author'] = cert[7] elif cert[5] == 'date': revdate = common.parse_timecert(cert[7]) rv['ago'] = common.ago(revdate) def BranchLink(self, ctxt, for_branch): rv = { 'type' : 'branch', 'branch' : for_branch, } branch = mtn.Branch(for_branch) changes, new_starting_point = Changes().branch_get_last_changes(ctxt.ops, branch, 0, 1) if len(changes) < 1: return web.notfound() if not changes: rv['error_string'] = 'no revisions in branch' else: rev, certs = changes[0] self.fill_from_certs(rv, certs) return rv def RevisionLink(self, ctxt, revision_id): rv = { 'type' : 'revision', 'revision_id' : revision_id, } rev = mtn.Revision(revision_id) certs = ctxt.ops.certs(rev) self.fill_from_certs(rv, certs) return rv def GET(self, ctxt, method, encoded_args): writer = json.JsonWriter() if not encoded_args.startswith('js_'): return web.notfound() args = json.read(binascii.unhexlify((encoded_args[3:]))) if hasattr(self, method): rv = getattr(self, method)(ctxt, *args) else: return web.notfound() print writer.write(rv) class BranchHead(object): def GET(self, ctxt, head_method, proxy_to, branch, extra_path): branch = mtn.Branch(branch) valid = ('browse', 'file', 'downloadfile', 'info', 'tar', 'graph', 'filechanges') if not proxy_to in valid: return web.notfound() heads = [head for head in ctxt.ops.heads(branch.name)] if len(heads) == 0: return web.notfound() def proxyurl(revision): return ctxt.perdb_join('revision/' + proxy_to + '/' + revision + urllib.quote(extra_path)) if len(heads) == 1 or head_method == 'anyhead': web.redirect(proxyurl(heads[0])) else: # present an option to the user to choose the head anyhead = '<a href="%s">link</a>' % (ctxt.perdb_join('branch/anyhead/' + \ proxy_to + '/' + urllib.quote(branch.name, safe = ''))) head_links = [] for revision in heads: author, date = '', '' for cert in ctxt.ops.certs(revision): if cert[4] == 'name' and cert[5] == 'date': date = cert[7] elif cert[4] == 'name' and cert[5] == 'author': author = mtn.Author(cert[7]) head_links.append('<a href="%s">%s</a> %s at %s' % (proxyurl(revision), revision.abbrev(), ctxt.link(author).html(), hq(date))) ctxt.render('branchchoosehead.html', page_title="Branch %s" % branch.name, branch=branch, proxy_to=proxy_to, anyhead=anyhead, head_links=head_links) class Databases(object): def GET(self, ctxt): databases = [] keys = ctxt.db_summary.keys() keys.sort() for k in keys: databases.append ({'name':k, 'description':ctxt.db_summary[k]}) ctxt.render('databases.html', page_title="Change database", databases=databases) class ChooseDatabase(object): def GET(self, factory): databases = [] keys = factory.db_summary.keys() keys.sort() for k in keys: databases.append ({'name':k, 'description':factory.db_summary[k]}) factory.renderer.render(factory, 'choosedb.html', page_title="Change database", databases=databases) class MimeIcon(object): def GET(self, type, sub_type): if not mimeicon: return web.notfound() mime_type = type+'/'+sub_type icon_file = mimeicon.lookup(mime_type) if icon_file: web.header('Content-Type', 'image/png') sys.stdout.write(open(icon_file).read()) else: return web.notfound() class RobotsTxt(object): def GET(self, ctxt): web.header('Content-Type', 'text/plain') print "User-agent: *" for dbname in [None] + ctxt.db_summary.keys(): for revision_page in ['tar', 'downloadfile', 'graph', 'file', 'browse', 'diff', 'info', 'graph']: # the goal is just to let a robot trawl through the most recent changes, and deny access # to expensive pointless things. We don't want a robot indexing every file in every revision, # as this is an enormous amount of information. for access_method in ['/revision/', '/branch/head/', '/branch/anyhead/', '/branch/changes/from/', '/json/', '/mimeicon/']: disallow = access_method + revision_page if not dbname is None: disallow = '/' + dbname + disallow print "Disallow:", disallow