PageRenderTime 40ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/peerreviewplugin/codereview/peerReviewBrowser.py

#
Python | 282 lines | 201 code | 46 blank | 35 comment | 51 complexity | cb1d74574053921f4b8f0b7d808ede0d MD5 | raw file
  1. #
  2. # Copyright (C) 2005-2006 Team5
  3. # All rights reserved.
  4. #
  5. # This software is licensed as described in the file COPYING.txt, which
  6. # you should have received as part of this distribution.
  7. #
  8. # Author: Team5
  9. #
  10. from __future__ import generators
  11. import re
  12. import urllib
  13. from trac import util
  14. from trac.core import *
  15. from trac.mimeview import *
  16. from trac.mimeview.api import IHTMLPreviewAnnotator
  17. from trac.perm import IPermissionRequestor
  18. from trac.web.chrome import ITemplateProvider
  19. from trac.web import IRequestHandler, RequestDone
  20. from trac.web.chrome import add_link, add_stylesheet
  21. from trac.wiki import wiki_to_html, wiki_to_oneliner, IWikiSyntaxProvider
  22. from trac.versioncontrol.web_ui.util import *
  23. from genshi.builder import tag
  24. IMG_RE = re.compile(r"\.(gif|jpg|jpeg|png)(\?.*)?$", re.IGNORECASE)
  25. CHUNK_SIZE = 4096
  26. DIGITS = re.compile(r'[0-9]+')
  27. def _natural_order(x, y):
  28. """Comparison function for natural order sorting based on
  29. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/214202."""
  30. nx = ny = 0
  31. while True:
  32. a = DIGITS.search(x, nx)
  33. b = DIGITS.search(y, ny)
  34. if None in (a, b):
  35. return cmp(x[nx:], y[ny:])
  36. r = (cmp(x[nx:a.start()], y[ny:b.start()]) or
  37. cmp(int(x[a.start():a.end()]), int(y[b.start():b.end()])))
  38. if r:
  39. return r
  40. nx, ny = a.end(), b.end()
  41. class peerReviewBrowser(Component):
  42. implements(IPermissionRequestor, IRequestHandler, IHTMLPreviewAnnotator)
  43. # ITextAnnotator methods
  44. def get_annotation_type(self):
  45. return 'lineno', 'Line', 'Line numbers'
  46. def get_annotation_data(self, context):
  47. return None
  48. def annotate_row(self, context, row, lineno, line, data):
  49. row.append(tag.th(id='L%s' % lineno)(
  50. tag.a(lineno, href='javascript:setLineNum(%s)' % lineno)
  51. ))
  52. # IPermissionRequestor methods
  53. def get_permission_actions(self):
  54. return ['BROWSER_VIEW', 'FILE_VIEW']
  55. # IRequestHandler methods
  56. def match_request(self, req):
  57. import re
  58. match = re.match(r'/(peerReviewBrowser|file)(?:(/.*))?', req.path_info)
  59. if match:
  60. req.args['path'] = match.group(2) or '/'
  61. if match.group(1) == 'file':
  62. # FIXME: This should be a permanent redirect
  63. req.redirect(self.env.href.peerReviewBrowser(req.args.get('path'),
  64. rev=req.args.get('rev'),
  65. format=req.args.get('format')))
  66. return True
  67. def process_request(self, req):
  68. path = req.args.get('path', '/')
  69. rev = req.args.get('rev')
  70. repos = self.env.get_repository(req.authname)
  71. try:
  72. node = get_existing_node(self.env, repos, path, rev)
  73. except:
  74. rev = repos.youngest_rev
  75. node = get_existing_node(self.env, repos, path, rev)
  76. hidden_properties = [p.strip() for p
  77. in self.config.get('browser', 'hide_properties',
  78. 'svk:merge').split(',')]
  79. req.hdf['title'] = path
  80. req.hdf['browser'] = {
  81. 'path': path,
  82. 'revision': rev or repos.youngest_rev,
  83. 'props': dict([(util.escape(name), util.escape(value))
  84. for name, value in node.get_properties().items()
  85. if not name in hidden_properties]),
  86. 'href': util.escape(self.env.href.peerReviewBrowser(path, rev=rev or
  87. repos.youngest_rev)),
  88. 'log_href': util.escape(self.env.href.log(path, rev=rev or None))
  89. }
  90. context = Context.from_request(req, 'source', path, node.created_rev)
  91. path_links = self.get_path_links_CRB(self.env.href, path, rev)
  92. if len(path_links) > 1:
  93. add_link(req, 'up', path_links[-2]['href'], 'Parent directory')
  94. req.hdf['browser.path'] = path_links
  95. if node.isdir:
  96. req.hdf['browser.is_dir'] = True
  97. self._render_directory(req, repos, node, rev)
  98. else:
  99. self._render_file(req, context, repos, node, rev)
  100. add_stylesheet(req, 'common/css/browser.css')
  101. return 'peerReviewBrowser.cs', None
  102. # Internal methods
  103. def get_path_links_CRB(self, href, path, rev):
  104. links = []
  105. parts = path.split('/')
  106. if not parts[-1]:
  107. parts.pop()
  108. path = '/'
  109. for part in parts:
  110. path = path + part + '/'
  111. links.append({
  112. 'name': part or 'root',
  113. 'href': util.escape(href.peerReviewBrowser(path, rev=rev))
  114. })
  115. return links
  116. def _render_directory(self, req, repos, node, rev=None):
  117. req.perm.assert_permission('BROWSER_VIEW')
  118. order = req.args.get('order', 'name').lower()
  119. req.hdf['browser.order'] = order
  120. desc = req.args.has_key('desc')
  121. req.hdf['browser.desc'] = desc and 1 or 0
  122. info = []
  123. for entry in node.get_entries():
  124. entry_rev = rev and entry.rev
  125. info.append({
  126. 'name': entry.name,
  127. 'fullpath': entry.path,
  128. 'is_dir': int(entry.isdir),
  129. 'content_length': entry.content_length,
  130. 'size': util.pretty_size(entry.content_length),
  131. 'rev': entry.rev,
  132. 'permission': 1, # FIXME
  133. 'log_href': util.escape(self.env.href.log(entry.path, rev=rev)),
  134. 'browser_href': util.escape(self.env.href.peerReviewBrowser(entry.path,
  135. rev=rev))
  136. })
  137. changes = get_changes(repos, [i['rev'] for i in info])
  138. def cmp_func(a, b):
  139. dir_cmp = (a['is_dir'] and -1 or 0) + (b['is_dir'] and 1 or 0)
  140. if dir_cmp:
  141. return dir_cmp
  142. neg = desc and -1 or 1
  143. if order == 'date':
  144. return neg * cmp(changes[b['rev']]['date_seconds'],
  145. changes[a['rev']]['date_seconds'])
  146. elif order == 'size':
  147. return neg * cmp(a['content_length'], b['content_length'])
  148. else:
  149. return neg * _natural_order(a['name'].lower(),
  150. b['name'].lower())
  151. info.sort(cmp_func)
  152. req.hdf['browser.items'] = info
  153. req.hdf['browser.changes'] = changes
  154. def _render_file(self, req, context, repos, node, rev=None):
  155. req.perm(context.resource).require('FILE_VIEW')
  156. changeset = repos.get_changeset(node.rev)
  157. req.hdf['file'] = {
  158. 'rev': node.rev,
  159. 'changeset_href': util.escape(self.env.href.changeset(node.rev)),
  160. 'date': util.format_datetime(changeset.date),
  161. 'age': util.pretty_timedelta(changeset.date),
  162. 'author': changeset.author or 'anonymous',
  163. 'message': wiki_to_html(changeset.message or '--', self.env, req,
  164. escape_newlines=True)
  165. }
  166. mime_type = node.content_type
  167. if not mime_type or mime_type == 'application/octet-stream':
  168. mime_type = get_mimetype(node.name) or mime_type or 'text/plain'
  169. # We don't have to guess if the charset is specified in the
  170. # svn:mime-type property
  171. ctpos = mime_type.find('charset=')
  172. if ctpos >= 0:
  173. charset = mime_type[ctpos + 8:]
  174. else:
  175. charset = None
  176. content = node.get_content()
  177. chunk = content.read(CHUNK_SIZE)
  178. format = req.args.get('format')
  179. if format in ('raw', 'txt'):
  180. req.send_response(200)
  181. req.send_header('Content-Type',
  182. format == 'txt' and 'text/plain' or mime_type)
  183. req.send_header('Content-Length', node.content_length)
  184. req.send_header('Last-Modified', util.http_date(node.last_modified))
  185. req.end_headers()
  186. while 1:
  187. if not chunk:
  188. raise RequestDone
  189. req.write(chunk)
  190. chunk = content.read(CHUNK_SIZE)
  191. else:
  192. # Generate HTML preview
  193. mimeview = Mimeview(self.env)
  194. # The changeset corresponding to the last change on `node`
  195. # is more interesting than the `rev` changeset.
  196. changeset = repos.get_changeset(node.rev)
  197. # add ''Plain Text'' alternate link if needed
  198. if not is_binary(chunk) and mime_type != 'text/plain':
  199. plain_href = req.href.browser(node.path, rev=rev, format='txt')
  200. add_link(req, 'alternate', plain_href, 'Plain Text',
  201. 'text/plain')
  202. # content = node.get_content().read(mimeview.max_preview_size)
  203. #if not is_binary(content):
  204. # if mime_type != 'text/plain':
  205. # plain_href = self.env.href.peerReviewBrowser(node.path,
  206. # rev=rev and node.rev,
  207. # format='txt')
  208. # add_link(req, 'alternate', plain_href, 'Plain Text',
  209. # 'text/plain')
  210. raw_href = self.env.href.peerReviewBrowser(node.path, rev=rev and node.rev,
  211. format='raw')
  212. preview_data = mimeview.preview_data(context, node.get_content(),
  213. node.get_content_length(),
  214. mime_type, node.created_path,
  215. raw_href,
  216. annotations=['lineno'])
  217. req.hdf['file'] = preview_data
  218. add_link(req, 'alternate', raw_href, 'Original Format', mime_type)
  219. add_stylesheet(req, 'common/css/code.css')
  220. return {
  221. 'changeset': changeset,
  222. 'size': node.content_length,
  223. 'preview': preview_data,
  224. 'annotate': False,
  225. }
  226. # ITemplateProvider methods
  227. def get_templates_dirs(self):
  228. """
  229. Return the absolute path of the directory containing the provided
  230. ClearSilver templates.
  231. """
  232. from pkg_resources import resource_filename
  233. return [resource_filename(__name__, 'templates')]
  234. def get_htdocs_dirs(self):
  235. from pkg_resources import resource_filename
  236. return [('hw', resource_filename(__name__, 'htdocs'))]