2018-11-04 17:58:16 +00:00
#!/usr/bin/python3
import mysql . connector as sql
import datetime as dt
2018-11-24 16:37:33 +00:00
import dateutil . relativedelta as rd
2018-11-04 17:58:16 +00:00
import sys
import re
2018-11-07 21:00:56 +00:00
import glob
2018-11-24 11:06:20 +00:00
import pytz
2018-11-04 17:58:16 +00:00
2018-11-24 16:37:33 +00:00
# Parameter: list of options in format key=value
# Returns: dictionary of given options in format
# {key : value, ...}
2018-11-04 17:58:16 +00:00
def parseOptions ( arr ) :
2018-11-24 16:37:33 +00:00
options = { }
for i in arr :
k = i . split ( ' = ' )
if len ( k ) == 1 :
options [ ' RAW ' ] = k [ 0 ]
else :
options [ k [ 0 ] ] = k [ 1 ]
return options
2018-11-04 17:58:16 +00:00
2018-12-11 20:27:48 +00:00
# Parameter: datetime object, interval string in {'today', 'week', '4weeks'}
2018-11-24 16:37:33 +00:00
# Returns: dictionary of events starting from baseDay, inside interval.
# {'NAME' : occurrence name,
# 'DATETIME' : date in format YYYY-MM-DD hh:mm:ss+tz_offset,
# 'ALLDAY' : boolean, true if event lasts all day. Hour in DATETIME has no meaning,
# 'LOCATION' : self explanatory,
# 'OCCURRENCE' : [optional] number of occurrence in repeated events}
2018-11-04 17:58:16 +00:00
def getEvents ( baseDay , interval ) :
2018-12-11 20:27:48 +00:00
if interval == ' today ' :
rightLimit = ( baseDay + dt . timedelta ( days = 1 ) )
elif interval == ' week ' :
2018-11-24 16:37:33 +00:00
rightLimit = ( baseDay + dt . timedelta ( weeks = 1 ) )
2018-11-04 17:58:16 +00:00
elif interval == ' 4weeks ' :
2018-11-24 16:37:33 +00:00
rightLimit = ( baseDay + dt . timedelta ( weeks = 4 ) )
2018-11-04 17:58:16 +00:00
else :
raise ValueError ( ' Invalid argument passed to getEvents ' )
2018-11-24 16:37:33 +00:00
leftLimit = baseDay
local_tz = pytz . timezone ( glob . cfg [ ' caldav ' ] [ ' local_tz ' ] )
leftLimit = local_tz . localize ( leftLimit )
rightLimit = local_tz . localize ( rightLimit )
2018-11-04 17:58:16 +00:00
2018-11-07 21:00:56 +00:00
c = sql . connect ( unix_socket = glob . cfg [ ' mysql ' ] [ ' unix_socket ' ] , host = glob . cfg [ ' mysql ' ] [ ' host ' ] , user = glob . cfg [ ' mysql ' ] [ ' user ' ] , password = glob . cfg [ ' mysql ' ] [ ' password ' ] , db = glob . cfg [ ' mysql ' ] [ ' db ' ] )
2018-11-04 17:58:16 +00:00
mycursor = c . cursor ( )
2018-11-24 16:37:33 +00:00
# For repeated events
query = " SELECT obj.calendardata FROM oc_calendarobjects AS obj INNER JOIN oc_calendars AS cal ON obj.calendarid = cal.id WHERE cal.displayname= ' %s ' AND obj.firstoccurence < %s AND obj.lastoccurence > %s " % ( glob . cfg [ ' caldav ' ] [ ' cal_name ' ] , rightLimit . strftime ( ' %s ' ) , leftLimit . strftime ( ' %s ' ) )
2018-11-04 17:58:16 +00:00
mycursor . execute ( query )
result = mycursor . fetchall ( )
c . close ( )
events = [ ]
for event in result :
2018-12-15 13:57:13 +00:00
repetition = { ' single ' : True , ' freq ' : { ' DAILY ' : 0 , ' WEEKLY ' : 0 , ' MONTHLY ' : 0 , ' YEARLY ' : 0 } , ' interval ' : 1 , ' count ' : 0 , ' until ' : None }
2018-11-24 16:37:33 +00:00
2018-11-24 11:01:49 +00:00
# selected only first column
event = event [ 0 ] . decode ( ' utf8 ' )
2018-11-04 17:58:16 +00:00
blockParsing = None
event_dict = { }
for item in event . split ( ' \r \n ' ) :
try :
k , v = item . split ( ' : ' , 1 )
except :
continue
k = k . split ( ' ; ' )
v = re . split ( ' (?<! \\ \\ ); ' , v )
k + = v
# Does not work well with nested blocks
# but nobody cares
if k [ 0 ] == " BEGIN " :
blockParsing = k [ 1 ]
elif k [ 0 ] == " END " :
blockParsing = None
else :
if blockParsing == " VEVENT " :
if k [ 0 ] == ' SUMMARY ' :
# save event name
event_dict [ ' NAME ' ] = k [ 1 ]
elif k [ 0 ] == ' DTSTART ' :
options = parseOptions ( k [ 1 : ] )
2018-11-24 11:06:20 +00:00
if ' TZID ' in options :
event_tz = pytz . timezone ( options [ ' TZID ' ] )
event_fmt = " % Y % m %d T % H % M % S "
# If TZID flag is not specified, datetime is UTC
else :
event_tz = pytz . timezone ( ' UTC ' )
event_fmt = " % Y % m %d T % H % M % SZ "
# Check if time is set and then localize it
2018-11-04 17:58:16 +00:00
if ' VALUE ' in options and options [ ' VALUE ' ] == ' DATE ' :
2018-11-24 16:37:33 +00:00
event_parsed_dt = dt . datetime . strptime ( options [ ' RAW ' ] , ' % Y % m %d ' )
event_dict [ ' DATETIME ' ] = local_tz . localize ( event_parsed_dt )
2018-11-04 17:58:16 +00:00
event_dict [ ' ALLDAY ' ] = True
else :
2018-11-24 14:58:56 +00:00
event_parsed_dt = dt . datetime . strptime ( options [ ' RAW ' ] , event_fmt )
event_parsed_dt = event_tz . localize ( event_parsed_dt )
2018-11-24 11:06:20 +00:00
event_dict [ ' DATETIME ' ] = event_parsed_dt . astimezone ( local_tz )
2018-11-04 17:58:16 +00:00
event_dict [ ' ALLDAY ' ] = False
2018-11-22 22:40:21 +00:00
elif k [ 0 ] == ' LOCATION ' :
event_dict [ ' LOCATION ' ] = k [ 1 ]
2018-11-04 17:58:16 +00:00
elif k [ 0 ] == ' RRULE ' :
2018-11-24 16:37:33 +00:00
options = parseOptions ( k [ 1 : ] )
repetition [ ' single ' ] = False
repetition [ ' freq ' ] [ options [ ' FREQ ' ] ] = 1
2018-12-11 20:27:48 +00:00
2018-11-24 16:37:33 +00:00
if ' INTERVAL ' in options :
repetition [ ' interval ' ] = int ( options [ ' INTERVAL ' ] )
2018-12-11 20:27:48 +00:00
2018-11-24 16:37:33 +00:00
if ' COUNT ' in options :
repetition [ ' count ' ] = int ( options [ ' COUNT ' ] )
2018-11-04 17:58:16 +00:00
2018-12-15 13:57:13 +00:00
if ' UNTIL ' in options :
try :
fmt = " % Y % m %d T % H % M % SZ "
repetition [ ' until ' ] = dt . datetime . strptime ( options [ ' UNTIL ' ] , fmt )
repetition [ ' until ' ] = local_tz . localize ( repetition [ ' until ' ] )
# Strip out time because is meaningless
repetition [ ' until ' ] = repetition [ ' until ' ] . date ( )
except :
repetition [ ' until ' ] = None
2020-04-17 17:46:45 +00:00
elif k [ 0 ] == ' CLASS ' :
# Store a boolean flag. True if PRIVATE
2020-04-17 20:35:17 +00:00
event_dict [ ' CLASS ' ] = k [ 1 ]
elif k [ 0 ] == ' STATUS ' :
event_dict [ ' STATUS ' ] = k [ 1 ]
2020-04-17 20:52:47 +00:00
elif k [ 0 ] == ' CATEGORIES ' :
if ' CATEGORIES ' not in event_dict :
event_dict [ ' CATEGORIES ' ] = [ ]
event_dict [ ' CATEGORIES ' ] + = [ k [ 1 ] ]
2018-11-24 16:37:33 +00:00
# If single event push into list
if repetition [ ' single ' ] == True :
events + = [ event_dict ]
else :
event_count = 1
# Get first event inside interval
while event_dict [ ' DATETIME ' ] < leftLimit :
event_dict [ ' DATETIME ' ] + = rd . relativedelta ( days = repetition [ ' freq ' ] [ ' DAILY ' ] , weeks = repetition [ ' freq ' ] [ ' WEEKLY ' ] , months = repetition [ ' freq ' ] [ ' MONTHLY ' ] , years = repetition [ ' freq ' ] [ ' YEARLY ' ] ) * repetition [ ' interval ' ]
event_count + = 1
2018-12-11 20:27:48 +00:00
2018-11-24 16:37:33 +00:00
# Push all events inside interval
2019-02-11 10:49:13 +00:00
while event_dict [ ' DATETIME ' ] < rightLimit and ( repetition [ ' until ' ] is None or repetition [ ' until ' ] > = event_dict [ ' DATETIME ' ] . date ( ) ) :
2018-11-24 16:37:33 +00:00
event_dict [ ' OCCURRENCE ' ] = event_count
events + = [ event_dict . copy ( ) ]
if repetition [ ' count ' ] == event_count :
break
event_count + = 1
event_dict [ ' DATETIME ' ] + = rd . relativedelta ( days = repetition [ ' freq ' ] [ ' DAILY ' ] , weeks = repetition [ ' freq ' ] [ ' WEEKLY ' ] , months = repetition [ ' freq ' ] [ ' MONTHLY ' ] , years = repetition [ ' freq ' ] [ ' YEARLY ' ] ) * repetition [ ' interval ' ]
2018-12-11 20:27:48 +00:00
2018-11-04 17:58:16 +00:00
# Thanks stackoverflow
# Return events sorted by date, AllDay first
return sorted ( events , key = lambda k : " %s %d " % ( k [ ' DATETIME ' ] , k [ ' ALLDAY ' ] == 0 ) )
2018-11-24 17:48:48 +00:00
# Only for test purposes
2018-11-04 17:58:16 +00:00
if __name__ == ' __main__ ' :
2018-11-24 17:48:48 +00:00
print ( " ZERo Optimized CALdav CAlendar Reader Engine " )
print ( " -------------------------------------------- " )
baseDay = dt . datetime . today ( )
interval = ' week '
if len ( sys . argv ) == 3 :
baseDay + = dt . timedelta ( days = int ( sys . argv [ 1 ] ) )
interval = sys . argv [ 2 ]
events = getEvents ( baseDay , interval )
for event in events :
try :
print ( " Event Name: %s " % event [ ' NAME ' ] )
print ( " Event Date: %s " % event [ ' DATETIME ' ] . date ( ) )
if event [ ' ALLDAY ' ] :
print ( " All Day " )
else :
print ( " Event Time: %s " % event [ ' DATETIME ' ] . time ( ) )
except :
print ( " Malformed event " )
print ( " ---------------- " )