# SimileTimeline module
# Copyright 2006 Noah Kantrowitz
from trac.core import *
from trac.web.chrome import ITemplateProvider, INavigationContributor, add_link, add_script, add_stylesheet
from trac.web.api import IRequestHandler
from trac.util.datefmt import format_date, format_time, http_date
from trac.util.html import html, Markup
from trac.util.text import to_unicode
from trac.Timeline import TimelineModule
import time, re
def add_abs_script(req, filename, mimetype='text/javascript'):
"""Add a reference to an external javascript file to the template."""
idx = 0
while True:
js = req.hdf.get('chrome.scripts.%i.href' % idx)
if not js:
break
if js == filename: # already added
return
idx += 1
req.hdf['chrome.scripts.%i' % idx] = {'href': filename, 'type': mimetype}
class SimileTimelineModule(Component):
implements(ITemplateProvider, IRequestHandler, INavigationContributor)
# IRequestHandler methods
def match_request(self, req):
return req.path_info.startswith('/stimeline')
def process_request(self, req):
req.perm.assert_permission('TIMELINE_VIEW')
format = req.args.get('format')
maxrows = int(req.args.get('max', 0))
# Parse the from date and adjust the timestamp to the last second of
# the day
t = time.localtime()
if req.args.has_key('from'):
try:
t = time.strptime(req.args.get('from'), '%x')
except:
pass
fromdate = time.mktime((t[0], t[1], t[2], 23, 59, 59, t[6], t[7], t[8]))
try:
daysback = max(0, int(req.args.get('daysback', '')))
except ValueError:
daysback = TimelineModule(self.env).default_daysback
req.hdf['timeline.from'] = format_date(fromdate)
req.hdf['timeline.daysback'] = daysback
available_filters = []
for event_provider in TimelineModule(self.env).event_providers:
available_filters += event_provider.get_timeline_filters(req)
filters = []
# check the request or session for enabled filters, or use default
for test in (lambda f: req.args.has_key(f[0]),
lambda f: req.session.get('timeline.filter.%s' % f[0], '')\
== '1',
lambda f: len(f) == 2 or f[2]):
if filters:
break
filters = [f[0] for f in available_filters if test(f)]
# save the results of submitting the timeline form to the session
if req.args.has_key('update'):
for filter in available_filters:
key = 'timeline.filter.%s' % filter[0]
if req.args.has_key(filter[0]):
req.session[key] = '1'
elif req.session.has_key(key):
del req.session[key]
stop = fromdate
start = stop - (daysback + 1) * 86400
events = []
for event_provider in TimelineModule(self.env).event_providers:
try:
events += event_provider.get_timeline_events(req, start, stop,
filters)
except Exception, e: # cope with a failure of that provider
self._provider_failure(e, req, event_provider, filters,
[f[0] for f in available_filters])
events.sort(lambda x,y: cmp(y[3], x[3]))
if maxrows and len(events) > maxrows:
del events[maxrows:]
req.hdf['title'] = 'Timeline'
# Get the email addresses of all known users
email_map = {}
for username, name, email in self.env.get_known_users():
if email:
email_map[username] = email
idx = 0
for kind, href, title, date, author, message in events:
event = {'kind': kind, 'title': re.sub(r'<[^>]*>','',unicode(title)), 'href': href,
'author': author or 'anonymous',
'date': format_date(date, '%m/%d/%Y'),
'time': format_time(date, '%H:%M'),
'message': message.replace('…','...'),
'icon': req.href.chrome('common',kind+'.png')}
if format == 'rss':
# Strip/escape HTML markup
if isinstance(title, Markup):
title = title.plaintext(keeplinebreaks=False)
event['title'] = title
event['message'] = to_unicode(message)
if author:
# For RSS, author must be an email address
if author.find('@') != -1:
event['author.email'] = author
elif email_map.has_key(author):
event['author.email'] = email_map[author]
event['date'] = http_date(date)
req.hdf['timeline.events.%s' % idx] = event
idx += 1
if format == 'rss':
return 'timeline_rss.cs', 'application/rss+xml'
if format == 'xml':
return 'stimeline_xml.cs', 'application/xml'
add_stylesheet(req, 'common/css/timeline.css')
rss_href = req.href.timeline([(f, 'on') for f in filters],
daysback=90, max=50, format='rss')
add_link(req, 'alternate', rss_href, 'RSS Feed', 'application/rss+xml',
'rss')
for idx,fltr in enumerate(available_filters):
req.hdf['timeline.filters.%d' % idx] = {'name': fltr[0],
'label': fltr[1], 'enabled': int(fltr[0] in filters)}
## NEW LINES
add_script(req, 'stimeline/js/simile/timeline-api.js')
#add_abs_script(req, "http://simile.mit.edu/timeline/api/timeline-api.js")
add_script(req, 'stimeline/js/simile.js')
xml_args = {'daysback': daysback,
'from': time.strftime('%x',time.localtime(fromdate)),
'format': 'xml',
}
xml_args.update(dict([(f, 'on') for f in filters]))
xml_href = req.href.stimeline(**xml_args)
req.hdf['stimeline.xml_href'] = Markup(xml_href)
req.hdf['stimeline.href'] = req.href.stimeline()
return 'stimeline.cs', None
def _provider_failure(self, exc, req, ep, current_filters, all_filters):
"""Raise a TracError exception explaining the failure of a provider.
At the same time, the message will contain a link to the timeline
without the filters corresponding to the guilty event provider `ep`.
"""
ep_name, exc_name = [i.__class__.__name__ for i in (ep, exc)]
guilty_filters = [f[0] for f in ep.get_timeline_filters(req)]
guilty_kinds = [f[1] for f in ep.get_timeline_filters(req)]
other_filters = [f for f in current_filters if not f in guilty_filters]
if not other_filters:
other_filters = [f for f in all_filters if not f in guilty_filters]
args = [(a, req.args.get(a)) for a in ('from', 'format', 'max',
'daysback')]
href = req.href.timeline(args+[(f, 'on') for f in other_filters])
raise TracError(Markup(
'%s event provider (%s) failed:
'
'%s: %s'
'
You may want to see the other kind of events from the ' 'Timeline
', ", ".join(guilty_kinds), ep_name, exc_name, to_unicode(exc), href)) # ITemplateProvider methods def get_templates_dirs(self): from pkg_resources import resource_filename return [resource_filename(__name__, 'templates')] def get_htdocs_dirs(self): from pkg_resources import resource_filename return [('stimeline', resource_filename(__name__, 'htdocs'))] # INavigationContributer methods def get_active_navigation_item(self, req): return 'stimeline' def get_navigation_items(self, req): if req.perm.has_permission('TIMELINE_VIEW'): yield ('mainnav', 'stimeline', html.A('Simile Timeline', href=req.href.stimeline()))