#!/usr/bin/python
# $Id: freebusy.py 6090 2007-02-20 15:44:06Z bremner $
# This festering piece of hackery was written by David Bremner, bremner@unb.ca
# it is placed in the public domain
#
# It may destroy all of your files, ruin your credit rating, put you on the
# no-fly lists of 103 nations and transform your favourite relative into
# a camel.
#
# Don't say I didn't warn you.
#
# If you're not scared off yet, you need the iCalendar python package
# from http://codespeak.net/icalendar, as well as pytz and elementtree
# packages this are semi-standard, and are e.g. in the debian repositories.
#
# you will also need to create a directory ~/.freebusy and put
# schedule.vfb there (e.g. export from Korganizer)
#
# Release 0.3
from icalendar import Calendar, Event
from datetime import timedelta,time,datetime
from pytz import timezone,utc
import elementtree.ElementTree as ET
from ConfigParser import ConfigParser
from os.path import expanduser
import os.path
daynames=['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
class calpage(object):
_config=dict(
busystyle='{ background-color: red ; border-style: solid; border-color: gray ; border-width: thin }',
idlestyle='{ background-color: gray }',
calfile=expanduser('~/.freebusy/schedule.vfb'),
outfile=expanduser('~/.freebusy/schedule.html'),
lastrun=expanduser('~/.freebusy/lastrun'),
tzname='UTC',
starthour=9,
dayhours=9,
maxlines=10,
deltaminutes=15,
pagetitle='Schedule',
alwaysrun=True
)
def __getattr__(self,attr):
return self._config[attr]
def __init__(self,**config):
self._day={}
for k,v in config.items():
if not self._config.has_key(k):
raise UserWarning('Unknown parameter '+k)
self._config[k]=v
self.localtz=timezone(self.tzname)
self.delta= timedelta(minutes=self.deltaminutes)
self._html_init()
def _html_init(self):
self._root = ET.Element("html")
head = ET.SubElement(self._root, "head")
style=ET.SubElement(head,"style",
{'type':'text/css'})
style.text='TD { text-align: center; border-style:none }'
style.text+= 'TD.busy '+self.busystyle
style.text+= 'TD.idle '+self.idlestyle
title = ET.SubElement(head, "title")
title.text = self.pagetitle
body = ET.SubElement(self._root, "body")
btitle=ET.SubElement(body,"h1")
btitle.text=self.pagetitle
ET.SubElement(body,"h2").text='Last modified '+datetime.now(self.localtz).isoformat()
self._table= ET.SubElement(body,"table")
def add_row(self,attr={}):
self._cur_row=ET.SubElement(self._table,"tr",attr)
def add_element(self,text,attr={}):
elt=ET.SubElement(self._cur_row,"td",attr)
elt.text=str(text)
def cal_header(self):
self.add_row()
self.add_element('')
self.add_element('')
for hour in range(self.starthour,self.starthour+self.dayhours):
self.add_element("%02d" % hour)
for skip in range(1,60/self.deltaminutes):
self.add_element('')
self.add_row()
self.add_element('')
self.add_element('')
for hour in range(self.starthour,self.starthour+self.dayhours):
self.add_element('')
for min in range(self.deltaminutes,60,self.deltaminutes):
self.add_element(min)
def cal_row(self,today):
hash=self._day[today]
starttime=time(hour=self.starthour)
startofday=datetime.combine(today,starttime)
startofday =self.localtz.localize(startofday).astimezone(utc)
endofday=startofday+timedelta(hours=self.dayhours)
minutes=0
daytime=startofday
while (daytime < endofday):
if hash.has_key(daytime.time()):
minutes+=self.deltaminutes
daytime+=self.delta
if minutes==0:
return
daytime=startofday
self.add_row()
self.add_element(today.isoformat());
self.add_element(daynames[today.weekday()])
while (daytime < endofday):
if hash.has_key(daytime.time()):
self.add_element('*',{'class':'busy'})
else:
self.add_element('_',{'class':'idle'})
daytime+=self.delta
def write(self,filename=None):
if (filename !=None):
self.outfile=filename
days=self._day.keys()
days.sort()
lines=self.maxlines+1
for today in days:
if lines>self.maxlines:
lines=1
self.cal_header()
else:
lines += 1
self.cal_row(today)
# wrap it in an ElementTree instance, and save as XML
tree = ET.ElementTree(self._root)
tree.write(self.outfile)
def process_interval(self,start,end):
date=start.date()
if not self._day.has_key(date):
self._day[date]={}
time=start
# note, assumption delta is less than 1 hour
offset=time.minute % (self.delta.seconds/60)
if ( offset != 0):
time=time.replace(minute=time.minute - offset)
while (time < end):
if time.date() != date:
date=time.date()
if not self._day.has_key(date):
self._day[date]={}
self._day[date][time.time()]=1
time += self.delta
def parse(self,name=None):
if (name != None):
self.calfile=name
cal = Calendar.from_string(open(self.calfile,'rb').read())
for component in cal.walk('VFREEBUSY'):
for start,end in component.decoded('FREEBUSY'):
self.process_interval(start,end)
def outofdate(self):
if self.alwaysrun:
return True
if not os.path.exists(self.outfile):
return True
if os.path.getmtime(self.outfile) <= os.path.getmtime(self.calfile):
return True
return False
config=ConfigParser()
config.read(expanduser("~/.freebusy/config"))
cdict={}
for section in config.sections():
for k,v in config.items(section):
cdict[k]=v
page=calpage(**cdict)
if (page.outofdate()):
page.parse()
page.write()