Browse Source

v0.2.2

master
Doug Le Tough 2 years ago
parent
commit
a582f8536b
4 changed files with 255 additions and 176 deletions
  1. +71
    -128
      calendarmgr/__init__.py
  2. +43
    -47
      displaymgr/__init__.py
  3. +12
    -1
      inputmgr/__init__.py
  4. +129
    -0
      utils/__init__.py

+ 71
- 128
calendarmgr/__init__.py View File

@ -1,69 +1,78 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import re
import copy
import arrow
import utils
import caldav
import inspect
import calendar
from icalendar import Event
from pytz import timezone
from datetime import datetime
from caldav.elements import dav, cdav
from dateutil.relativedelta import relativedelta
class Calendarmgr(object):
event = {
'VCALENDAR': {
'VERSION': None,
'PRODID': None,
'CALSCALE': None,
},
'VEVENT': {
'DTSAMP': None,
'UID': None,
'SEQUENCE': None,
'SUMMARY': None,
'DTSTART': None,
'DTEND': None,
'STATUS': None,
'ORGANIZER': None,
'LOCATION': None,
'DESCRIPTION': None,
},
'VALARM': {
'TRIGGER': None,
'ACTION': None,
'DESCRIPTION': None,
'DTSTAMP': None,
}
}
class Eventsmgr(object):
def __init__(self, tz, logger):
self.tz = tz
self.logger = logger
self.events = []
self.current_event = 0
def get_events(self, cals, start, end, max_event_width):
try:
self.logger.info("[+] Retrieving events [{} -> {}]".format(start, end))
self.events = []
for cal in cals:
self.logger.info(" '-> {}".format(cal.name))
events = cal.date_search(start.to('UTC').datetime, end.to('UTC').datetime)
for event in events:
self.logger.info(" '--> {}".format(str(event).split('/')[-1]))
dict_event = utils.event_as_dict(event)
pp_event = utils.preprocess_event(dict_event, start, self.tz, max_event_width)
self.events.append(pp_event)
return self.events
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def get_next_event(self):
if len(self.events):
if self.current_event + 1 < len(self.events):
self.current_event += 1
return self.current_event
self.current_event = 0
return self.current_event
return None
todo = {
'VCALENDAR': {
'VERSION': None,
'PRODID': None,
'CALSCALE': None,
},
'VTODO': {
'DTSTAMP': None,
'UID': None,
'CREATED': None,
'LAST-MODIFIED': None,
'SUMMARY': None,
'STATUS': None,
}
}
def get_previous_event(self):
if len(self.events):
if self.current_event - 1 >= 0:
self.current_event -= 1
return self.current_event
self.current_event = len(self.events) - 1
return self.current_event
return None
class Todosmgr(object):
def __init__(self, logger):
self.logger = logger
self.todos = []
self.current_todo = 0
def get_todos(self, cals, max_todo_width):
self.logger.info("[+] Retrieving todos")
self.todos = []
for cal in cals:
self.logger.info(" '-> {}".format(cal.name))
for todo in cal.todos():
self.logger.info(" '--> {}".format(str(todo).split('/')[-1]))
self.todos.append(utils.todo_as_dict(todo))
class Calendarmgr(object):
def __init__(self, scheme, url, user, passwd, collection, tz, logger):
self.logger = logger
self.collection = collection
self.tz = timezone(tz)
self.datetime = datetime.now(self.tz).replace(hour=0, minute=0,second=0, microsecond=0)
self.tz = tz
self.datetime = arrow.now().floor('day')
self.calendar = None
self.caldav = None
self.events = []
self.todos = []
self.eventsmgr = Eventsmgr(self.tz, self.logger)
self.todosmgr = Todosmgr(self.logger)
self.scheme = scheme
self.url = url
self.user = user
@ -84,79 +93,13 @@ class Calendarmgr(object):
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def str_multireplace(self, string , replacements):
substrs = sorted(replacements, key=len, reverse=True)
regexp = re.compile('|'.join(map(re.escape, substrs)))
return regexp.sub(lambda match: replacements[match.group(0)], string)
def event_as_dict(self, event):
def get_calendars(self, max_event_width, max_todo_width):
try:
section = None
header = None
patterns = {"\\n": " ", "\\": ""}
dict_event = copy.deepcopy(self.event)
for line in event.data.split("\r\n"):
if len(line):
try:
if line.startswith("BEGIN:"):
section = line[line.index(":")+1:]
elif line.startswith(" "):
dict_event[section][header] += self.str_multireplace(line[1:], patterns)
else:
# Do not split on ":" because line may have several ":"
header = line[:line.index(":")]
dict_event[section][header] = self.str_multireplace(line[line.index(":")+1:], patterns)
except ValueError as e:
if line.startswith("ATTENDEE"):
dict_event[section][header] += " {}".format(self.str_multireplace(line, patterns))
continue
raise(Exception("WTFError: CalDav is shit: {}".format(str(e))))
return copy.deepcopy(dict_event)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def todo_as_dict(self, todo):
try:
section = None
header = None
patterns = {"\\n": " ", "\\": ""}
dict_todo = copy.deepcopy(self.todo)
for line in todo.data.split("\n"):
if len(line):
try:
if line.startswith("BEGIN:"):
section = line[line.index(":")+1:]
elif line.startswith(" "):
dict_todo[section][header] += self.str_multireplace(line[1:], patterns)
else:
# Do not split on ":" because line may have several ":"
header = line[:line.index(":")]
dict_todo[section][header] = self.str_multireplace(line[line.index(":")+1:], patterns)
except KeyError as e:
# We don't need most of todos bullshit
continue
return copy.deepcopy(dict_todo)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def get_events(self):
try:
self.events = []
self.todos = []
start = self.datetime.replace(tzinfo=self.tz).astimezone(timezone('UTC'))
end = start + relativedelta(days=1)
self.logger.info("[+] Retrieving events from caldav: [%s -> %s]" % (start, end))
for cal in self.caldav:
self.logger.info(" '-> {} [In collection: {}]".format(cal.name, cal.name in self.collection))
if cal.name in self.collection:
events = cal.date_search(start, end)
for event in events:
self.logger.info(" '--> Event: {}".format(str(event).split('/')[-1]))
self.events.append(self.event_as_dict(event))
for todo in cal.todos():
self.logger.info(" '--> Todo: {}".format(str(todo).split('/')[-1]))
self.todos.append(self.todo_as_dict(todo))
return(self.events, self.todos)
end = self.datetime.shift(days=+1)
self.logger.info("[+] Retrieving calendars from caldav: [%s -> %s]" % (self.datetime, end))
self.eventsmgr.get_events(self.caldav, self.datetime, end, max_event_width)
self.todosmgr.get_todos(self.caldav, max_todo_width)
return (self.eventsmgr.events, self.todosmgr.todos)
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
@ -170,17 +113,17 @@ class Calendarmgr(object):
return (self.datetime, cal)
def get_calendar_prevday(self):
new_date = self.datetime - relativedelta(days=1)
new_date = self.datetime.shift(days=-1)
return self.get_calendar_month_days(new_date)
def get_calendar_nextday(self):
new_date = self.datetime + relativedelta(days=1)
new_date = self.datetime.shift(days=+1)
return self.get_calendar_month_days(new_date)
def get_calendar_prevmonth_days(self):
new_date = self.datetime - relativedelta(months=1)
new_date = self.datetime.shift(months=-1)
return self.get_calendar_month_days(new_date)
def get_calendar_nextmonth_days(self):
new_date = self.datetime + relativedelta(months=1)
new_date = self.datetime.shift(months=+1)
return self.get_calendar_month_days(new_date)

+ 43
- 47
displaymgr/__init__.py View File

@ -5,6 +5,7 @@ import curses
import inspect
from pytz import timezone
from datetime import datetime
from operator import itemgetter
from dateutil.relativedelta import relativedelta
class Displaymgr(object):
@ -18,6 +19,8 @@ class Displaymgr(object):
self.events_x = 42
self.events_width = self.calendar_width - self.events_x - 1
self.events_height = self.calendar_height - 2
self.events = []
self.todos = []
self.tz = timezone(tz)
self.today = None
self.logger = logger
@ -80,22 +83,6 @@ class Displaymgr(object):
self.logger.info("[+] Screen refresh")
curses.doupdate()
def format_event(self, event, max_width):
self.logger.info("[+] formating event")
lines = []
for header in event:
lline = []
for line in header.split("\n"):
for word in line.split():
lline.append(word)
if max_width < len(" ".join(lline)):
lline.pop()
lines.append(" ".join(lline))
lline = [word]
continue
lines.append(" ".join(lline))
return lines
def display_help(self, mode="nav"):
try:
base_help = "TAB: Switch mode\t\tR: Refresh\t\tQ: Quit"
@ -155,49 +142,58 @@ class Displaymgr(object):
self.logger.info("[+] Displaying events")
max_height, max_width = self.windows['events'][0].getmaxyx()
empty_msg = len(self.fetch_msg) * " "
events = sorted(events, key=lambda k: k['VEVENT']['DTSTART'])
self.events = sorted(events, key=itemgetter(0))
v_offset = 3
clear_msg = " " * len(self.wait_msg)
self.windows["events"][0].addstr(4, int(max_width/2)-int(len(clear_msg)/2)+2, "{}".format(clear_msg))
self.refresh_window("events")
for event in events:
try:
tz = timezone('UTC')
start = end = None
if event['VEVENT']['DTSTART']:
start = tz.localize(datetime.strptime(event['VEVENT']['DTSTART'], '%Y%m%dT%H%M%SZ')).astimezone(self.tz)
if event['VEVENT']['DTEND']:
end = tz.localize(datetime.strptime(event['VEVENT']['DTEND'], '%Y%m%dT%H%M%SZ')).astimezone(self.tz)
if start is None and end is None:
start = self.today
end = start + relativedelta(hours=23, minutes=59)
start = datetime.strftime(start, '%H:%M')
end = datetime.strftime(end, '%H:%M')
date = "{} -> {} [{}]".format(start, end, event['VALARM']['TRIGGER'])
location = "Location: {}".format(event['VEVENT']['LOCATION'])
summary = "Summary: {}".format(event['VEVENT']['SUMMARY'])
description = "Description: {}".format(event['VEVENT']['DESCRIPTION'])
event = self.format_event([date, location, summary, description], max_width-4)
try:
for event in self.events:
for line in event:
self.windows['events'][0].addstr(v_offset, 2, line)
v_offset += 1
self.windows['events'][0].hline(v_offset, 2, curses.ACS_HLINE, max_width-4)
v_offset += 1
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
self.refresh_window('events')
self.refresh_window('events')
except Exception as e:
self.logger.error('[%s %s()]: %s "%s"' % (self.__class__, inspect.currentframe().f_code.co_name, e.__class__, e))
def display_todos(self, todos):
self.logger.info("[+] Displaying todos")
self.clear_window("todo")
max_height, max_width = self.windows['todo'][0].getmaxyx()
index = 0
if todos:
for todo in todos:
ttodo = todo["VTODO"]["SUMMARY"]
if len(ttodo) > max_width - 6:
ttodo = todo["VTODO"]["SUMMARY"][0:max_width - 6]
self.windows['todo'][0].addch(index + 2, 2, curses.ACS_BULLET)
self.windows['todo'][0].addstr(index + 2, 4, ttodo)
index += 1
self.refresh_window('todo')
for todo in todos:
ttodo = todo["VTODO"]["SUMMARY"]
if len(ttodo) > max_width - 6:
ttodo = todo["VTODO"]["SUMMARY"][0:max_width - 6]
self.windows['todo'][0].addch(index + 2, 2, curses.ACS_BULLET)
self.windows['todo'][0].addstr(index + 2, 4, ttodo)
index += 1
self.refresh_window('todo')
def select_event(self, event):
self.logger.info("[+] Selecting event: {}".format(event))
max_height, max_width = self.windows['events'][0].getmaxyx()
if event is not None:
v_offset = 3
event = self.events[event]
for e in self.events:
if e == event:
for line in e:
self.windows['events'][0].addch(v_offset, 0, curses.ACS_VLINE)
self.windows['events'][0].addch(v_offset, 1, curses.ACS_VLINE)
v_offset += 1
v_offset += 1
else:
for line in e:
self.windows['events'][0].addstr(v_offset, 0, " ")
self.windows['events'][0].addstr(v_offset, 1, " ")
v_offset += 1
v_offset += 1
self.refresh_window("events")
def select_todo(self, todo):
self.logger.info("[+] Selecting todo: {}".format(todo))
if event:
pass

+ 12
- 1
inputmgr/__init__.py View File

@ -120,31 +120,42 @@ class Inputmgr(object):
self.logger.info("[+] Refreshing calendar")
if not self.calendarmgr.caldav:
self.calendarmgr.init_caldav()
events, todos = self.calendarmgr.get_events()
max_event_height, max_event_width = self.displaymgr.windows['events'][0].getmaxyx()
max_todo_height, max_todo_width = self.displaymgr.windows['todo'][0].getmaxyx()
events, todos = self.calendarmgr.get_calendars(max_event_width - 4, max_todo_width)
self.displaymgr.display_events(events)
self.displaymgr.display_todos(todos)
def next_event(self):
self.logger.info("[+] Going to next event")
self.displaymgr.select_event(self.calendarmgr.eventsmgr.get_next_event())
def prev_event(self):
self.logger.info("[+] Going to previous event")
self.displaymgr.select_event(self.calendarmgr.eventsmgr.get_previous_event())
def next_todo(self):
self.logger.info("[+] Going to next todo")
self.displaymgr.select_todo(1)
def prev_todo(self):
self.logger.info("[+] Going to previous todo")
self.displaymgr.select_todo(-1)
def add_event(self):
self.logger.info("[+] Adding event")
def edit_event(self):
self.logger.info("[+] Editing event")
def delete_event(self):
self.logger.info("[+] Deleting event")
def add_todo(self):
self.logger.info("[+] Adding todo")
def mark_todo(self):
self.logger.info("[+] Marking todo")
def delete_todo(self):
self.logger.info("[+] Delete todo")

+ 129
- 0
utils/__init__.py View File

@ -0,0 +1,129 @@
#!/usr/bin/env python3
# -*- coding: utf-8
import re
import copy
import arrow
template_event = {
'VCALENDAR': {
'VERSION': None,
'PRODID': None,
'CALSCALE': None,
},
'VEVENT': {
'DTSAMP': None,
'UID': None,
'SEQUENCE': None,
'SUMMARY': None,
'DTSTART': None,
'DTEND': None,
'STATUS': None,
'ORGANIZER': None,
'LOCATION': None,
'DESCRIPTION': None,
},
'VALARM': {
'TRIGGER': None,
'ACTION': None,
'DESCRIPTION': None,
'DTSTAMP': None,
}
}
template_todo = {
'VCALENDAR': {
'VERSION': None,
'PRODID': None,
'CALSCALE': None,
},
'VTODO': {
'DTSTAMP': None,
'UID': None,
'CREATED': None,
'LAST-MODIFIED': None,
'SUMMARY': None,
'STATUS': None,
}
}
def str_multireplace(string , replacements):
substrs = sorted(replacements, key=len, reverse=True)
regexp = re.compile('|'.join(map(re.escape, substrs)))
return regexp.sub(lambda match: replacements[match.group(0)], string)
def event_as_dict(event):
section = None
header = None
patterns = {"\\n": " ", "\\": ""}
dict_event = copy.deepcopy(template_event)
for line in event.data.split("\r\n"):
if len(line):
try:
if line.startswith("BEGIN:"):
section = line[line.index(":")+1:]
elif line.startswith(" "):
dict_event[section][header] += str_multireplace(line[1:], patterns)
else:
# Do not split on ":" because line may have several ":"
header = line[:line.index(":")]
dict_event[section][header] = str_multireplace(line[line.index(":")+1:], patterns)
except ValueError as e:
if line.startswith("ATTENDEE"):
dict_event[section][header] += " {}".format(str_multireplace(line, patterns))
continue
raise(Exception("WTFError: CalDav is shit: {}".format(str(e))))
return copy.deepcopy(dict_event)
def preprocess_event(event, today, local_tz, max_width):
tz = "UTC"
start = end = None
if event['VEVENT']['DTSTART']:
start = arrow.get(event['VEVENT']['DTSTART'], 'YYYYMMDDTHHmmss').to(local_tz).strftime('%H:%M')
if event['VEVENT']['DTEND']:
end = arrow.get(event['VEVENT']['DTEND'], 'YYYYMMDDTHHmmss').to(local_tz).strftime('%H:%M')
if start is None and end is None:
start = today.floor('day').to(local_tz).strftime('%H:%M')
end = today.shift(hours=23, minutes=59).strftime('%H:%M')
date = "{} -> {} [{}]".format(start, end, event['VALARM']['TRIGGER'])
location = "Location: {}".format(event['VEVENT']['LOCATION'])
summary = "Summary: {}".format(event['VEVENT']['SUMMARY'])
description = "Description: {}".format(event['VEVENT']['DESCRIPTION'])
event = format_event([date, location, summary, description], max_width)
return event
def format_event(event, max_width):
lines = []
for header in event:
lline = []
for line in header.split("\n"):
for word in line.split():
lline.append(word)
if max_width < len(" ".join(lline)):
lline.pop()
lines.append(" ".join(lline))
lline = [word]
continue
lines.append(" ".join(lline))
return lines
def todo_as_dict(todo):
section = None
header = None
patterns = {"\\n": " ", "\\": ""}
dict_todo = copy.deepcopy(template_todo)
for line in todo.data.split("\n"):
if len(line):
try:
if line.startswith("BEGIN:"):
section = line[line.index(":")+1:]
elif line.startswith(" "):
dict_todo[section][header] += str_multireplace(line[1:], patterns)
else:
# Do not split on ":" because line may have several ":"
header = line[:line.index(":")]
dict_todo[section][header] = str_multireplace(line[line.index(":")+1:], patterns)
except KeyError as e:
# We don't need most of todos bullshit
continue
return copy.deepcopy(dict_todo)

Loading…
Cancel
Save