=== trac/Timeline.py ================================================================== --- trac/Timeline.py (revision 3750) +++ trac/Timeline.py (local) @@ -31,6 +31,7 @@ from trac.versioncontrol.svn_authz import SubversionAuthorizer from trac.web.chrome import add_link, add_stylesheet, INavigationContributor from trac.web.main import IRequestHandler +from trac.Rpc import IXmlRpcHandler class ITimelineEventProvider(Interface): @@ -60,10 +61,16 @@ class TimelineModule(Component): - implements(INavigationContributor, IPermissionRequestor, IRequestHandler) + implements(INavigationContributor, IPermissionRequestor, IRequestHandler, IXmlRpcHandler) event_providers = ExtensionPoint(ITimelineEventProvider) + def xmlrpc_module(self): + return 'timeline' + + def xmlrpc_methods(self): + return ['listFilters'] + # INavigationContributor methods def get_active_navigation_item(self, req): @@ -85,6 +92,13 @@ def match_request(self, req): return req.path_info == '/timeline' + def listFilters(self, req): + req.perm.assert_permission('TIMELINE_VIEW') + available_filters = [] + for event_provider in self.event_providers: + available_filters += event_provider.get_timeline_filters(req) + return available_filters + def process_request(self, req): req.perm.assert_permission('TIMELINE_VIEW') @@ -109,9 +123,7 @@ req.hdf['timeline.from'] = time.strftime('%x', time.localtime(fromdate)) req.hdf['timeline.daysback'] = daysback - available_filters = [] - for event_provider in self.event_providers: - available_filters += event_provider.get_timeline_filters(req) + available_filters = self.listFilters(req) filters = [] # check the request or session for enabled filters, or enable all @@ -167,9 +179,9 @@ if author: # For RSS, author must be an email address - if author.find('@') != -1: + if '@' in author: event['author.email'] = author - elif author in email_map.keys(): + elif email_map.has_key(author): event['author.email'] = email_map[author] event['date'] = http_date(time.mktime(t)) === trac/web/cgi_frontend.py ================================================================== --- trac/web/cgi_frontend.py (revision 3750) +++ trac/web/cgi_frontend.py (local) @@ -83,6 +83,11 @@ """ get = cgi.FieldStorage.getvalue + def __init__(self, *args, **kwds): + cgi.FieldStorage.__init__(self, *args, **kwds) + if self.list is None: + self.list = [] + def __setitem__(self, name, value): if self.has_key(name): del self[name] === trac/web/chrome.py ================================================================== --- trac/web/chrome.py (revision 3750) +++ trac/web/chrome.py (local) @@ -87,6 +87,27 @@ """ +class PluginTemplateMixin(Component): + implements(ITemplateProvider) + # ITemplateProvider + + def get_htdocs_dir(self): + """Return the absolute path of a directory containing additional + static resources (such as images, style sheets, etc). + """ + from pkg_resources import resource_filename + return resource_filename(self.__class__.__module__, 'htdocs') + + def get_templates_dir(self): + """Return the absolute path of the directory containing the provided + ClearSilver templates. + """ + from pkg_resources import resource_filename + self.log.debug('Htdocs: %s' % self.__class__) + self.log.debug(__name__) + return resource_filename(self.__class__.__module__, 'templates') + + class Chrome(Component): """Responsible for assembling the web site chrome, i.e. everything that is not actual page content. === trac/web/main.py ================================================================== --- trac/web/main.py (revision 3750) +++ trac/web/main.py (local) @@ -19,6 +19,8 @@ # # Author: Christopher Lenz +import traceback + import mimetypes import os import os.path @@ -297,6 +299,8 @@ """ from trac import __version__ from time import gmtime, localtime, strftime + if hdf is None: + return hdf['trac'] = { 'version': __version__, 'time': strftime('%c', localtime()), @@ -452,6 +456,8 @@ if env and env.log: env.log.exception(e) populate_hdf(req.hdf, env, req) + env.log.error('Request args: %s' % req.args) + populate_hdf(req.hdf, env, req) if isinstance(e, TracError): req.hdf['title'] = e.title or 'Error' @@ -482,6 +488,7 @@ if env and env.log: env.log.error('Failed to render pretty error page: %s', e2, exc_info=True) + env.log.error(traceback.format_exc()) req.send_response(500) req.send_header('Content-Type', 'text/plain') req.end_headers() === trac/web/modpython_frontend.py ================================================================== --- trac/web/modpython_frontend.py (revision 3750) +++ trac/web/modpython_frontend.py (local) @@ -150,7 +150,12 @@ self.args = '' def __getattr__(self, name): return getattr(self.req, name) - util.FieldStorage.__init__(self, RequestWrapper(req), keep_blank_values=1) + try: + util.FieldStorage.__init__(self, RequestWrapper(req), keep_blank_values=1) + except apache.SERVER_RETURN, e: + self.value = req.read() + else: + self.value = '' # Populate FieldStorage with the original query string parameters, if # they aren't already defined through the request body === trac/wiki/model.py ================================================================== --- trac/wiki/model.py (revision 3750) +++ trac/wiki/model.py (local) @@ -40,6 +40,8 @@ self.version = -1 self.text = '' self.readonly = 0 + self.time = 0 + self.author = '' self.old_text = self.text self.old_readonly = self.readonly @@ -48,23 +50,27 @@ db = self.env.get_db_cnx() cursor = db.cursor() if version: - cursor.execute("SELECT version,text,readonly FROM wiki " + cursor.execute("SELECT version,text,readonly,time,author FROM wiki " "WHERE name=%s AND version=%s", (name, int(version))) else: - cursor.execute("SELECT version,text,readonly FROM wiki " + cursor.execute("SELECT version,text,readonly,time,author FROM wiki " "WHERE name=%s ORDER BY version DESC LIMIT 1", (name,)) row = cursor.fetchone() if row: - version,text,readonly = row + version,text,readonly,time,author = row self.version = int(version) self.text = text self.readonly = readonly and int(readonly) or 0 + self.time = int(time) + self.author = author else: self.version = -1 self.text = '' self.readonly = 0 + self.time = 0 + self.author = '' exists = property(fget=lambda self: self.version >= 0) === trac/Rpc.py ================================================================== --- trac/Rpc.py (revision 3750) +++ trac/Rpc.py (local) @@ -0,0 +1,193 @@ +import xmlrpclib +import sys + +from trac.core import * +from trac.perm import IPermissionRequestor +from trac.web.main import IRequestHandler +from trac.wiki.api import WikiSystem +from trac.wiki.model import WikiPage + +class IXmlRpcHandler(Interface): + def xmlrpc_module(): + pass + + def xmlrpc_methods(): + pass + +class XmlRpcModule(Component): + + implements(IPermissionRequestor, IRequestHandler, IXmlRpcHandler) + rpc_handlers = ExtensionPoint(IXmlRpcHandler) + + def get_permission_actions(self): + return ['XML_RPC'] + + def match_request(self, req): + return req.path_info == '/xmlrpc' + + def process_request(self, req): + req.send_response(200) + req.send_header('Content-Type', 'text/xml') + req.end_headers() + + try: + req.perm.assert_permission('XML_RPC') + + params, method = xmlrpclib.loads(str(req.args.value)) + + result = self._call(method, params, req) + if result is None: + raise xmlrpclib.Fault(4, 'Method call did not return a value') + req.write(xmlrpclib.dumps((result,), methodresponse=True)) + except: + self.log.error(sys.exc_value) + req.write(xmlrpclib.dumps(self._wrap_except())) + + def _get_module(self, rpc_module): + for rpc in self.rpc_handlers: + if rpc.xmlrpc_module() == rpc_module: + return rpc + + def _get_method(self, method): + parts = method.split('.', 1) + if len(parts) < 2: + return None + + moduleName, methodName = parts + module = self._get_module(moduleName) + if module and methodName in module.xmlrpc_methods(): + return getattr(module, methodName) + + def _call(self, methodName, params, req): + func = self._get_method(methodName) + if func: + return func(req, *params) + else: + raise xmlrpclib.Fault(2, 'Unknown method') + + def _wrap_except(self): + e = sys.exc_value + if isinstance(e, xmlrpclib.Fault): + return e + return xmlrpclib.Fault(1, '%s:%s' % (sys.exc_type, e)) + + def xmlrpc_module(self): + return 'system' + + def xmlrpc_methods(self): + return ['listMethods', + 'methodHelp', + 'methodSignature', + 'multicall'] + + def listMethods(self, req): + methods = [] + for rpc in self.rpc_handlers: + module = rpc.xmlrpc_module() + for method in rpc.xmlrpc_methods(): + methods.append('.'.join((module, method))) + return methods + + def methodHelp(self, req, methodName): + func = self._get_method(methodName) + if func: + import pydoc + return pydoc.getdoc(func) + else: + raise xmlrpclib.Fault(3, "Can't get help for unknown method: %s" % methodName) + + def methodSignature(self, req, methodName): + return 'system.methodSignature is not implemented' + + def multicall(self, req, callParams): + results = [] + for meth_sig in callParams: + try: + r = [self._call(**meth_sig)] + except: + r = self._wrap_except() + results.append(r) + return results + + +class WikiRpc(Component): + implements(IXmlRpcHandler) + + def __init__(self): + self.wiki = WikiSystem(self.env) + + def xmlrpc_module(self): + return 'wiki' + + def xmlrpc_methods(self): + return ['getRecentChanges', + 'getRPCVersionSupported', + 'getPage', 'getPageVersion', + 'getPageInfo', 'getPageInfoVersion', + 'getAllPages'] + + def _to_timestamp(self, datetime): + import time + return time.mktime(time.strptime(datetime.value, '%Y%m%dT%H:%M:%S')) + + def _page_info(self, name, time, author, version): + return dict(name=name, lastModified=xmlrpclib.DateTime(int(time)), + author=author, version=int(version)) + + def getRecentChanges(self, req, since): + req.perm.assert_permission('WIKI_VIEW') + since = self._to_timestamp(since) + db = self.env.get_db_cnx() + cursor = db.cursor() + cursor.execute('SELECT name, max(time), author, version FROM wiki' + ' WHERE time >= %s GROUP BY name ORDER BY max(time) DESC', since) + result = [] + for name, time, author, version in cursor: + result.append(self._page_info(name, time, author, version)) + return result + + def getRPCVersionSupported(self, req): + req.perm.assert_permission('WIKI_VIEW') + return 2 + + def getPage(self, req, pagename, version=None): + req.perm.assert_permission('WIKI_VIEW') + page = WikiPage(self.env, pagename, version) + if page.exists: + return page.text + else: + msg = 'Wiki page "%s" does not exist' % pagename + if version is not None: + msg += ' at version %s' % version + raise xmlrpclib.Fault(0, msg) + + getPageVersion = getPage + + def getPageHTML(self, req, pagename, version=None): + req.perm.assert_permission('WIKI_VIEW') + pass + + getPageHTMLVersion = getPageHTML + + def getAllPages(self, req): + ''' + Returns a list of all pages. + The result is an array of utf8 pagenames. + ''' + req.perm.assert_permission('WIKI_VIEW') + return list(self.wiki.get_pages()) + + def getPageInfo(self, req, pagename, version=None): + req.perm.assert_permission('WIKI_VIEW') + page = WikiPage(self.env, pagename, version) + if page.exists: + return self._page_info(page.name, page.time, page.author, + page.version) + + getPageInfoVersion = getPageInfo + + def listLinks(self, req, pagename): + req.perm.assert_permission('WIKI_VIEW') + pass + +