/usr/share/pyshared/icalview/icalview.py is in trac-icalviewplugin 0.7889-1.1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 | """
plugin fro Trac 0.11 to provide iCalendar ouput for ticket's queries
author : Xavier Pechoultes <x.pechoultres@clariprint.com>
Licence: GPL
"""
import os
import sys
import re
import datetime
import time
from trac.core import *
from trac.util.translation import _
from trac.util.datefmt import format_datetime,localtz,to_datetime
from trac.web import IRequestHandler
from trac.ticket.query import QueryModule
from trac.ticket import Milestone
from trac.mimeview.api import IContentConverter
from trac.web.chrome import INavigationContributor
from cStringIO import StringIO
from trac.resource import Resource, get_resource_url
from trac.util.text import CRLF
class iCalViewPlugin(QueryModule):
implements(IRequestHandler, INavigationContributor,
IContentConverter)
"""Plugin class """
def match_request(self, req):
return req.path_info == '/ical'
def convert_content(self, req, mimetype, query, key):
return self.export_ical(req, query)
def get_supported_conversions(self):
yield ('ical', _('iCalendar'), 'ics',
'trac.ticket.Query', 'text/calendar', 8)
def convert_content(self, req, mimetype, query, key):
if key == 'ical':
return self.export_ical(req, query)
def get_active_navigation_item(self, req):
return 'iCalendar'
def parse_date(self,d):
"""
parse a user date by using standard or user config formats
use `short_date_format` and `date_time_format` config values
different formats can be defined separated by `;`
"""
date_format = self.config['icalendar'].get('short_date_format','%m/%d/%Y')
date_time_format = self.config['icalendar'].get('date_time_format','%m/%d/%Y %H:%M')
for fmt in date_format.split(";"):
try:
ts = time.strptime(d, fmt)
return datetime.date(ts[0],ts[1],ts[2])
except:
self.env.log.debug(d + " not match " + fmt)
for fmt in date_time_format.split(";"):
try:
ts = time.strptime(d, date_time_format)
return datetime.datetime(ts[0],ts[1],ts[2],ts[3],ts[4],ts[5],0, localtz)
except:
self.env.log.debug(d + " not match " + date_time_format)
return None
def parse_duration(self,d):
"""
parse a user duration
if the duration begin by a 'P', we presume that it math RFC specification for duration
the user may set a number of x days (our hours) by ending the duration with a `d` (our `h`) : `2d` (`2h`)
we support also standar H:M definition.
"""
if d[0] == "P" :
return d
val = re.match("([0-9]{1,})d",d)
if val :
n_days = int(val.groups()[0])
return datetime.timedelta(n_days+1)
val = re.match("([0-9]{1,})h",d)
if val :
n_hours = int(val.groups()[0])
return datetime.timedelta(0, 0,0,0, 0,n_hours)
val = re.match("([0-9]{1,}):([0-9]{2})",d)
if val :
n_hours = int(val.groups()[0])
n_minutes = int(val.groups()[1])
return datetime.timedelta(0, 0,0,0,n_minutes,n_hours)
return datetime.timedelta(1)
def format_date(self,content,propname,d,force_date=False,tzinfo=None):
if type(d) == datetime.datetime and force_date == False:
tz = tzinfo or localtz
t = to_datetime(d, tzinfo).astimezone(tz)
content.write("%s:%s\r\n" % (propname,t.strftime("%Y%m%dT%H%M%S")))
elif type(d) == datetime.datetime and force_date == True:
tz = tzinfo or localtz
t = to_datetime(d, tzinfo).astimezone(tz)
content.write("%s;VALUE=DATE:%s\r\n" % (propname,t.strftime("%Y%m%d")))
else:
content.write("%s;VALUE=DATE:%s\r\n" % (propname,d.strftime("%Y%m%d")))
def export_ical(self, req, query):
"""
return the icalendar file
"""
dtstart_key = self.config['icalendar'].get('dtstart','')
duration_key = self.config['icalendar'].get('duration','')
self.env.log.debug("dtstart_key=%s" % dtstart_key)
self.env.log.debug("duration_key=%s" % duration_key)
if dtstart_key != '':
self.env.log.debug("use dtstart_key=%s" % dtstart_key)
if dtstart_key not in query.cols:
query.cols.append(dtstart_key)
if duration_key != '':
if duration_key not in query.cols:
query.cols.append(duration_key)
if 'priority' not in query.cols:
query.cols.append('priority')
if 'description' not in query.cols:
query.cols.append('description')
if 'changetime' not in query.cols:
query.cols.append('changetime')
if 'time' not in query.cols:
query.cols.append('time')
query.max = sys.maxint
results = query.execute(req)
cols = query.get_columns()
content = StringIO()
content.write('BEGIN:VCALENDAR\r\n')
content.write('VERSION:2.0\r\n')
content.write('PRODID:2.0:-//Edgewall Software//NONSGML Trac 0.11//EN\r\n')
content.write('METHOD:PUBLISH\r\n')
content.write('X-WR-CALNAME: test\r\n')
attr_map = {
"summary" : "SUMMARY",
"type" : "CATEGORIES",
"description" : "DESCRIPTION"
}
priority_map = {
'blocker' : 1,
'critical' : 2,
'major' : 6,
'minor' : 8,
'trivial' : 9
}
custom_priority_map = self.config['icalendar'].get('priority_map',None)
if custom_priority_map != None:
for m in custom_priority_map.split(","):
k = m.split(":")
if len(k) == 2:
priority_map[k[0]] = int(k[1])
def write_prop(name, value, params={}):
propname = ';'.join([name] + [k + '=' + v for k, v in params.items()])
propvalue = value.replace('\n','\n ').replace('\r\n \r\n','\r\n')
content.write("%s:%s\r\n" % (propname,propvalue))
for result in results:
ticket = Resource('ticket', result['id'])
if 'TICKET_VIEW' in req.perm(ticket):
kind = "VEVENT"
dtstart = None
if dtstart_key != '':
dtstart = self.parse_date(result[dtstart_key])
due = None
if dtstart == None :
kind = "VTODO"
self.env.log.debug("is TODO")
if result.has_key("milestone"):
milestone_key = result["milestone"]
if milestone_key != "--" and milestone_key != "":
self.env.log.debug("Milestone !" + milestone_key)
milestone = Milestone(self.env,milestone_key)
due = milestone.due
content.write("BEGIN:%s\r\n" % kind)
content.write("UID:<%s@%s>\r\n" % (get_resource_url(self.env,ticket,req.href),os.getenv('SERVER_NAME')))
if dtstart != None:
self.format_date(content,"DTSTART",dtstart)
if duration_key != '':
duration = self.parse_duration(result[duration_key])
if type(duration) == datetime.timedelta :
content.write("DURATION:P%dDT%dS\r\n" % (duration.days, duration.seconds))
else :
content.write("DURATION:%s\r\n" % duration)
elif due != None:
self.format_date(content,"DUE",due,False)
self.format_date(content,"CREATED",result["time"])
self.format_date(content,"DTSTAMP",result["changetime"])
protocol = "http"
if os.getenv('SERVER_PROTOCOL') != None :
if "HTTPS" in os.getenv('SERVER_PROTOCOL') :
protocol = "https"
content.write("URL:%s://%s%s\r\n" % (protocol,os.getenv('SERVER_NAME'),get_resource_url(self.env,ticket,req.href)))
priority = None
try:
priority = priority_map[result['priority']]
except:
self.env.log.debug("unmapped priority %s" % result['priority'])
if priority != None:
content.write("PRIORITY;VALUE=%s:%d\r\n" % (result['priority'],priority))
for key in attr_map:
if key in cols:
write_prop(attr_map[key], unicode(result[key]).encode('utf-8'),{})
content.write("END:%s\r\n" % kind)
content.write('END:VCALENDAR\r\n')
return content.getvalue(), 'text/calendar;charset=utf-8'
|