From f962eceb8915d8f90d49512e831b70c23cc0a935 Mon Sep 17 00:00:00 2001 From: Gentile G Date: Fri, 22 Sep 2023 13:12:40 +0200 Subject: [PATCH] Refactoring of caldav_recurring_task --- .gitignore | 3 ++ README.md | 45 ++++++++++---------- configuration.example.yml | 5 +++ configuration.yml | 4 -- dv-task-manager.py | 90 --------------------------------------- main.py | 85 ++++++++++++++++++++++++++++++++++++ requirements.txt | 4 ++ tasks.example.yml | 54 +++++++++++++++++++++++ tasks.yml | 69 ------------------------------ 9 files changed, 174 insertions(+), 185 deletions(-) create mode 100644 .gitignore create mode 100644 configuration.example.yml delete mode 100644 configuration.yml delete mode 100755 dv-task-manager.py create mode 100755 main.py create mode 100644 requirements.txt create mode 100644 tasks.example.yml delete mode 100644 tasks.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..53b1930 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +configuration.yml +tasks.yml diff --git a/README.md b/README.md index 255817f..f5b0637 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,30 @@ -# Dorfsvald PIM +# CalDav Recurring Tasks Generator -## Téléphone: +This script is made to be ran once a day. It will create CalDav tasks as described in file tasks.yml. The tasks are created 2 days ahead of their tasks -### Syncevolution +## Configuration -''' -sudo -u radicale /usr/bin/syncevo-http-server http://127.0.0.1:9000/syncml --start-dbus-session --debug -''' +Update configuration.yml file with caldav server url, calendar name, username and password -### Filtres +Describe recurring tasks in tasks.yml file -Event: -- Enlever event passés d'un mois + task_name: + start: + freq: MONTHLY + bymonthday: -5 + duration: + days: 5 + title: A title for the task + body: A body for the task + priority: 1 + +See https://dateutil.readthedocs.io/en/stable/rrule.html for more information on the start section syntax +## Usage + + ./main.py + +## TODO Task: -- Enlever tasks terminée -- ? - -Journal: -- Ajouter une ligne 5871-DTSTART;VALUE=DATE:20161205 - -Contacts: -- Enelver contacts sans tel / email -- Simplifier les contacts - - -### Requirements -caldav pyyaml ipdb icalendar +- Remove completed tasks on new occurence creation +- Allow the script to run several times on the same day diff --git a/configuration.example.yml b/configuration.example.yml new file mode 100644 index 0000000..7a23b4c --- /dev/null +++ b/configuration.example.yml @@ -0,0 +1,5 @@ +dav_server: + url: https://caldav.server.url/ + user: username_example + pass: password_example + calendar: calendar_name diff --git a/configuration.yml b/configuration.yml deleted file mode 100644 index 4db09b2..0000000 --- a/configuration.yml +++ /dev/null @@ -1,4 +0,0 @@ -dav_server: - url: http://127.0.0.1:5232/webdav/ - user: ggentile - pass: ID0T'8h6 diff --git a/dv-task-manager.py b/dv-task-manager.py deleted file mode 100755 index 1f4271d..0000000 --- a/dv-task-manager.py +++ /dev/null @@ -1,90 +0,0 @@ -#! /home/ggentile/perso/projects/python/Envs/dorfsvald-pim/bin/python3 - -import os -import datetime -import calendar - -from yaml import load -from icalendar.parser import Contentlines, Contentline -from dateutil.rrule import rrule, FREQNAMES -import caldav - - -class Task: - def __init__(self, ref_date, name, data): - self.ref_date = ref_date - self.name = name - data['start']['freq'] = FREQNAMES.index(data['start']['freq']) - self.date_begin = rrule(count=1, dtstart=ref_date, **data['start'])[0] - self.date_end = self.date_begin + datetime.timedelta(microseconds=-1, **data['duration']) - - 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') - uid = "DV-Taskmanager-{}-{}".format(self.name, self.date_end.strftime('%Y%m%d')) - c = Contentlines([ - Contentline('BEGIN:VCALENDAR'), - Contentline('VERSION:2.0'), - Contentline('PRODID:-//DorfsvaldNet//Pim Atutoask Client//FR'), - Contentline('BEGIN:VTODO'), - Contentline('DTSTAMP:%s' % current_dtstamp), - Contentline('UID:%s' % uid), - Contentline('CREATED:%s' % current_dtstamp), - Contentline('LAST-MODIFIED:%s' % current_dtstamp), - Contentline('SEQUENCE:%s' % '4'), - Contentline('DESCRIPTION:%s' % self.body), - Contentline('SUMMARY:%s' % self.title), - Contentline('PRIORITY:%s' % self.priority), - Contentline('DUE;VALUE=DATE:%s' % self.date_end.strftime('%Y%m%d')), - Contentline('DTSTART;VALUE=DATE:%s' % 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 - - if self.calendar is None: - raise LookupError('No calendar named "{}"'.format(calendar_name)) - - def add_event(self, event): - self.calendar.add_event(task.to_ical()) - - -# conf = load('./configuration.yml') - -ref_date = datetime.date.today() + datetime.timedelta(days=2) -with open('./tasks.yml', 'r') as content_file: - tasks_conf = load(content_file.read()) - -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) - -username="ggentile" -password="ID0t'8h6" -url = "https://cloud.dorfsvald.net/webdav/ggentile/" -calendar = "calendar" - - -client = Client(url, username, password, calendar) -for task in task_list: - client.add_event(task) - -print('process finished: {} tasks created'.format(len(task_list))) diff --git a/main.py b/main.py new file mode 100755 index 0000000..d2ff64b --- /dev/null +++ b/main.py @@ -0,0 +1,85 @@ +import datetime + +from yaml import load, FullLoader +from icalendar.parser import Contentlines, Contentline +from dateutil.rrule import rrule, FREQNAMES +import caldav + + +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.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') + uid = f"CaldavRecurringTask-{self.name}-{self.date_end.strftime('%Y%m%d')}" + 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:{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 add_event(self, new_task): + self.calendar.add_event(new_task.to_ical()) + + +if __name__ == "__main__": + with open('./configuration.yml', 'r') as configuration_file: + conf = load(configuration_file.read(), Loader=FullLoader) + + ref_date = datetime.date.today() + datetime.timedelta(days=2) + with open('tasks.example.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']) + for task in task_list: + client.add_event(task) + + print('process finished: {} tasks created'.format(len(task_list))) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ffee207 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +pyyaml +icalendar +caldav +python-dateutil diff --git a/tasks.example.yml b/tasks.example.yml new file mode 100644 index 0000000..f8d9ee9 --- /dev/null +++ b/tasks.example.yml @@ -0,0 +1,54 @@ +# see http://dateutil.readthedocs.io/en/stable/rrule.html +# freq, dtstart=None, interval=1, wkst=None, count=None, until=None, bysetpos=None, +# bymonth=None, bymonthday=None, byyearday=None, byeaster=None, byweekno=None, byweekday=None, +# byhour=None, byminute=None, bysecond=None, cache=False +# Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, or SECONDLY. + +monthly_task: + start: + freq: MONTHLY + bymonthday: 1 + duration: + days: 10 + title: Monthly task + body: A task that is created once a month and starts on the first day of the month + priority: 1 + +other_monthly_task: + start: + freq: MONTHLY + bymonthday: -5 + duration: + days: 5 + title: Another Monthly task + body: A task that is created once a month and starts five days before the end of the month + priority: 1 + +daily_task: + start: + freq: DAILY + duration: + days: 1 + title: A Daily task + body: This a task that is created everyday + priority: 1 + +yearly_task: + start: + freq: YEARLY + byyearday: 10 + duration: + days: 60 + title: A Yearly task + body: This a task that is created once a year, on the tenth day of the year + priority: 1 + +another_yearly_task: + start: + freq: YEARLY + byweekno: 4 + duration: + days: 60 + title: A Yearly task + body: This a task that is created once a year, on the fourth week of the year + priority: 1 diff --git a/tasks.yml b/tasks.yml deleted file mode 100644 index 1ad0010..0000000 --- a/tasks.yml +++ /dev/null @@ -1,69 +0,0 @@ -# see http://dateutil.readthedocs.io/en/stable/rrule.html -# freq, dtstart=None, interval=1, wkst=None, count=None, until=None, bysetpos=None, -# bymonth=None, bymonthday=None, byyearday=None, byeaster=None, byweekno=None, byweekday=None, -# byhour=None, byminute=None, bysecond=None, cache=False -# Where freq must be one of YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, or SECONDLY. - -reglement_piano: - start: - freq: MONTHLY - bymonthday: -5 - duration: - days: 5 - title: Règlement Piano - body: 460713 - priority: 1 -loyer: - start: - freq: MONTHLY - bymonthday: 1 - duration: - days: 10 - title: Loyer - body: https://fr.foncia.com/login\nA797GEA4EP23F - priority: 1 -declaration_polemploi: - start: - freq: MONTHLY - bymonthday: 1 - duration: - days: 10 - title: Déclaration Pôle Emploi - body: 3851096Z\n847323 - priority: 1 -declaration_ae: - start: - freq: MONTHLY - bymonthday: 1 - bymonth: [1,4,7,10] - duration: - days: 30 - title: Déclaration AE - body: 51450594000031 - priority: 1 -facturation_x2i: - start: - freq: MONTHLY - bymonthday: -10 - duration: - days: 10 - title: Facture X2I - body: N/A - priority: 1 -declaration_impots: - start: - freq: YEARLY - byyearday: 90 - duration: - days: 60 - title: Déclaration impôts - body: 1480545752413 - priority: 1 -#test: - #start: - #freq: DAILY - #duration: - #days: 60 - #title: test_test_new_test - #body: 1480545752413 - #priority: 1