Files
caldav_recurring_task/main.py
2023-09-26 11:19:08 +02:00

125 lines
4.7 KiB
Python
Executable File

import datetime
import os
from yaml import load, FullLoader
from icalendar.parser import Contentlines, Contentline
from dateutil.rrule import rrule, FREQNAMES
import caldav
from caldav.lib.error import NotFoundError
APP_ID = 'CaldavRecurringTask'
class Task:
def __init__(self, reference_date, name, data):
self.ref_date = reference_date
self.name = name
data['start']['freq'] = FREQNAMES.index(data['start']['freq'])
self.date_begin = rrule(count=1, dtstart=reference_date, **data['start'])[0]
self.date_end = self.date_begin + datetime.timedelta(microseconds=-1, **data['duration'])
self.uid = f"{APP_ID}-{self.name}-{self.date_begin.strftime('%Y%m%d')}"
self.title = data['title']
self.body = data['body']
self.priority = data['priority']
def to_ical(self):
current_dtstamp = datetime.datetime.now().strftime('%Y%m%dT%H%M%SZ')
c = Contentlines([
Contentline('BEGIN:VCALENDAR'),
Contentline('VERSION:2.0'),
Contentline('PRODID:-//DorfsvaldNet//CaldavRecuringTaskClient//FR'),
Contentline('BEGIN:VTODO'),
Contentline(f'DTSTAMP:{current_dtstamp}'),
Contentline(f'UID:{self.uid}'),
Contentline(f'CREATED:{current_dtstamp}'),
Contentline(f'LAST-MODIFIED:{current_dtstamp}'),
Contentline(f'SEQUENCE:{"4"}'),
Contentline(f'DESCRIPTION:{self.body}'),
Contentline(f'SUMMARY:{self.title}'),
Contentline(f'PRIORITY:{self.priority}'),
Contentline(f'DUE;VALUE=DATE:{self.date_end.strftime("%Y%m%d")}'),
Contentline(f'DTSTART;VALUE=DATE:{self.date_begin.strftime("%Y%m%d")}'),
Contentline('PERCENT-COMPLETE:0'),
Contentline('STATUS:NEEDS-ACTION'),
Contentline('END:VTODO'),
Contentline('END:VCALENDAR')
])
return c.to_ical()
class Client:
def __init__(self, url, username, password, calendar_name):
self.client = caldav.DAVClient(url, username=username, password=password)
self.principal = self.client.principal()
self.calendar = None
for c in self.principal.calendars():
if c.name == calendar_name:
self.calendar = c
break
if self.calendar is None:
raise LookupError('No calendar named "{}" found'.format(calendar_name))
def todos(self, include_completed=True):
return self.calendar.todos(include_completed=include_completed)
def add_todo(self, new_task):
self.calendar.add_todo(new_task.to_ical())
def get_todo_by_uid(self, uid):
try:
todo = self.calendar.todo_by_uid(uid)
return todo
except NotFoundError:
return False
if __name__ == "__main__":
with open('./configuration.yml', 'r') as configuration_file:
conf = load(configuration_file.read(), Loader=FullLoader)
last_run_file_path = './.last.run'
if os.path.isfile(last_run_file_path):
with open(last_run_file_path, 'r') as last_run_file:
run_date_str = last_run_file.readline()
run_date = datetime.datetime.strptime(run_date_str, '%Y-%m-%d') + datetime.timedelta(days=1)
run_date = run_date.date()
else:
run_date = datetime.date.today()
while run_date <= datetime.date.today():
ref_date = run_date + datetime.timedelta(days=2)
with open('tasks.yml', 'r') as content_file:
tasks_conf = load(content_file.read(), Loader=FullLoader)
task_list = []
for task_name, task_data in tasks_conf.items():
t = Task(ref_date, task_name, task_data)
if t.date_begin.date() == ref_date:
task_list.append(t)
if task_list:
server = conf['dav_server']
client = Client(server['url'], server['user'], server['pass'], server['calendar'])
previous_tasks = client.todos(include_completed=True)
for task in task_list:
if not client.get_todo_by_uid(task.uid):
# Removing previous completed tasks
for t in previous_tasks:
if t.icalendar_component.get('uid').startswith(f"{APP_ID}-{task.name}")\
and t.icalendar_component.get('status') == 'COMPLETED':
t.delete()
client.add_todo(task)
print('process finished for date {}: {} tasks created\n'.format(run_date, len(task_list)))
run_date = run_date + datetime.timedelta(days=1)
with open(last_run_file_path, 'w') as last_run_file:
last_run_file.write(str(datetime.date.today()))