Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically install your own local calDav calendar host. #55

Open
a-t-0 opened this issue Aug 2, 2021 · 4 comments
Open

Automatically install your own local calDav calendar host. #55

a-t-0 opened this issue Aug 2, 2021 · 4 comments
Labels
calendar help wanted Extra attention is needed Quality

Comments

@a-t-0
Copy link
Contributor

a-t-0 commented Aug 2, 2021

      <p>Currently google calendar gets all the data on where you are, what you're gonna do there, when you are there.</p>

Replace google calendar by installing your won caldav (and/or) davdroid server: https://www.davx5.com/.

@a-t-0
Copy link
Contributor Author

a-t-0 commented Aug 2, 2021

      <p>I think it would be nice to keep the data in an CalDav, (or if Caldav is not actually a calendare storage unit but merely the synchronizing tool: nextcloud (so on your own pc, iso some other cloud)). However, I think there might be some people that can automatically set up such a calendar host on a windows pc or wsl significantly faster than me due to more experience, which would allow me to focus on topics more related to the core of this repository. So if you're reading this and think you'd know how to start setting up your own calender host (open source, e.g. caldav) please feel free to comment and/or pick it up!</p>

If relevant: I have a technitiumDNS server (Thanks to the creator of the awesome software!) ready to be set up (relatively easily), I think it is automatable to set up (at least locally, not in the domain host, but that could be changed if a free (annual) domain server is added with an api that allows one to enter a custom dns server from command line). Explanation is here in the readme https://github.com/a-t-0/DnsServer.

@a-t-0
Copy link
Contributor Author

a-t-0 commented Aug 2, 2021

      <p>This <a href="https://codereview.stackexchange.com/questions/167227/creating-ics-calendar-events-from-json-like-file" rel="nofollow">code review</a> wrote a code to parse taskwarrior files in <code>.json</code> format and generate ICS calendar events from it. Perhaps also find the source on github.</p>

The original code is:

#!/usr/bin/env python3

import os.path
from ics import Calendar, Event
import json
import sys
import os
import io

taskwarrior_formatted_data_location = "/var/taskd/tx.data"
ics_calendar_full_path = "/var/www/html/tasks.ics"
ics_calendar_full_path_chores = "/var/www/html/chores.ics"
ics_calendar_full_path_announcements = "/var/www/html/announcements.ics"
unique_list_of_calendar_tasks = []

valid_statuses_for_calendar = ['pending', 'waiting']
invalid_statuses_for_calendar = ['completed', 'deleted']

def create_uniqueness(task):
""" creates a definition of uniqueness from a task's attributes
input: task: an object with a Taskwarrior set of attributes
output: a string of the unique signature of this task """

if not task:
    return None

if task.get('due') and task.get('description'):
    return task['due'] + task['description']
else:
    return task['uuid']

def is_unique_calendar_task(task):
""" if this task exists in the list of tasks to make a calendar already
input: task: an object with a Taskwarrior set of attributes
output: boolean - true if the task is unique, false if it already existed in the list """

if not task:
    return None
if task['status'] in invalid_statuses_for_calendar:
    return False

if task.get('status') and task['status'] in valid_statuses_for_calendar:
    unique_task_id = create_uniqueness(task)
    if unique_task_id in unique_list_of_calendar_tasks:
        return False
    unique_list_of_calendar_tasks.append(unique_task_id)
    return True
return False

def create_task_calendar_description(task):
""" creates a custom description of the task for the calendar summary
input: task: an object with a Taskwarrior set of attributes
output: string to be used for the calendar event summary """
project = "{} {} {}".format("[", task['project'], "] ") if task.get('project') else ""
tags = " [" + ", ".join([k for k in task['tags'] if 'cal.' not in k]) + "]" if (task.get('tags') and [k for k in
task['tags'] if 'cal.' not in k]) else ""
return project + task['description'] + tags

def get_task_first_calendar(task):
""" find the first cal.<xyz> tag, which indicates which calendar this task should appear on. Defaults to the
general calendar
input: task: an object with a Taskwarrior set of attributes
output: string with the name of the calendar this event should go on """
if task.get('tags') is None:
return ""
cals = [s for s in task['tags'] if 'cal.' in s]
if not cals:
return ""
return cals[0].replace("cal.", "")

def get_unique_task():
""" read the JSON-like file, filtering out lines I don't need, and calling the unique function to create tasks to
be processed
input: none
output: yields a unique task """
real_lines = []
for line in io.open(taskwarrior_formatted_data_location, 'r', encoding='utf8'):
li = line.strip()
if li.startswith("{"):
real_lines.append(li)

lines_as_string = "[" + ",".join(real_lines) + "]"
for task in json.loads(lines_as_string):
    if is_unique_calendar_task(task):
        yield task

def get_task_start_date_for_event(task):
""" find the calendar event start date based on a hierarchy of which date to use
input: task: an object with a Taskwarrior set of attributes
output: date to use in Taskwarrior format """
if task is None:
return ""
if task.get('due'):
return task['due']
if task.get('scheduled'):
return task['scheduled']
if task.get('wait'):
return task['wait']
else:
return ""

if name == "main":
general_cal = Calendar(creator="My TaskWarrior Calendar")
chores_cal = Calendar(creator="My TaskWarrior Chores Calendar")
ann_cal = Calendar(creator="My TaskWarrior Announcements Calendar")
for task in get_unique_task():

    event_due = get_task_start_date_for_event(task)
    if event_due in (None, ""):
        continue
    cal_event = Event()
    cal_event.begin = event_due
    cal_event.name = create_task_calendar_description(task)


    task_first_calendar = get_task_first_calendar(task)
    if task_first_calendar == "":
        general_cal.events.append(cal_event)
    if task_first_calendar == "chores":
        chores_cal.events.append(cal_event)
    if task_first_calendar == "announcements":
        ann_cal.events.append(cal_event)


with open(os.path.expanduser(ics_calendar_full_path), 'w') as f:
    f.writelines(general_cal)

with open(os.path.expanduser(ics_calendar_full_path_chores), 'w') as f:
    f.writelines(chores_cal)

with open(os.path.expanduser(ics_calendar_full_path_announcements), 'w') as f:
    f.writelines(ann_cal)

sys.exit(0)


<clipboard-copy aria-label="Copy" class="ClipboardButton btn js-clipboard-copy m-2 p-0 tooltipped-no-delay" data-copy-feedback="Copied!" data-tooltip-direction="w" value="#!/usr/bin/env python3

import os.path
from ics import Calendar, Event
import json
import sys
import os
import io

taskwarrior_formatted_data_location = "/var/taskd/tx.data"
ics_calendar_full_path = "/var/www/html/tasks.ics"
ics_calendar_full_path_chores = "/var/www/html/chores.ics"
ics_calendar_full_path_announcements = "/var/www/html/announcements.ics"
unique_list_of_calendar_tasks = []

valid_statuses_for_calendar = ['pending', 'waiting']
invalid_statuses_for_calendar = ['completed', 'deleted']

def create_uniqueness(task):
""" creates a definition of uniqueness from a task's attributes
input: task: an object with a Taskwarrior set of attributes
output: a string of the unique signature of this task """

if not task:
    return None

if task.get('due') and task.get('description'):
    return task['due'] + task['description']
else:
    return task['uuid']

def is_unique_calendar_task(task):
""" if this task exists in the list of tasks to make a calendar already
input: task: an object with a Taskwarrior set of attributes
output: boolean - true if the task is unique, false if it already existed in the list """

if not task:
    return None
if task['status'] in invalid_statuses_for_calendar:
    return False

if task.get('status') and task['status'] in valid_statuses_for_calendar:
    unique_task_id = create_uniqueness(task)
    if unique_task_id in unique_list_of_calendar_tasks:
        return False
    unique_list_of_calendar_tasks.append(unique_task_id)
    return True
return False

def create_task_calendar_description(task):
""" creates a custom description of the task for the calendar summary
input: task: an object with a Taskwarrior set of attributes
output: string to be used for the calendar event summary """
project = "{} {} {}".format("[", task['project'], "] ") if task.get('project') else ""
tags = " [" + ", ".join([k for k in task['tags'] if 'cal.' not in k]) + "]" if (task.get('tags') and [k for k in
task['tags'] if 'cal.' not in k]) else ""
return project + task['description'] + tags

def get_task_first_calendar(task):
""" find the first cal. tag, which indicates which calendar this task should appear on. Defaults to the
general calendar
input: task: an object with a Taskwarrior set of attributes
output: string with the name of the calendar this event should go on """
if task.get('tags') is None:
return ""
cals = [s for s in task['tags'] if 'cal.' in s]
if not cals:
return ""
return cals[0].replace("cal.", "")

def get_unique_task():
""" read the JSON-like file, filtering out lines I don't need, and calling the unique function to create tasks to
be processed
input: none
output: yields a unique task """
real_lines = []
for line in io.open(taskwarrior_formatted_data_location, 'r', encoding='utf8'):
li = line.strip()
if li.startswith("{"):
real_lines.append(li)

lines_as_string = &quot;[&quot; + &quot;,&quot;.join(real_lines) + &quot;]&quot;
for task in json.loads(lines_as_string):
    if is_unique_calendar_task(task):
        yield task

def get_task_start_date_for_event(task):
""" find the calendar event start date based on a hierarchy of which date to use
input: task: an object with a Taskwarrior set of attributes
output: date to use in Taskwarrior format """
if task is None:
return ""
if task.get('due'):
return task['due']
if task.get('scheduled'):
return task['scheduled']
if task.get('wait'):
return task['wait']
else:
return ""

if name == "main":
general_cal = Calendar(creator="My TaskWarrior Calendar")
chores_cal = Calendar(creator="My TaskWarrior Chores Calendar")
ann_cal = Calendar(creator="My TaskWarrior Announcements Calendar")
for task in get_unique_task():

    event_due = get_task_start_date_for_event(task)
    if event_due in (None, &quot;&quot;):
        continue
    cal_event = Event()
    cal_event.begin = event_due
    cal_event.name = create_task_calendar_description(task)


    task_first_calendar = get_task_first_calendar(task)
    if task_first_calendar == &quot;&quot;:
        general_cal.events.append(cal_event)
    if task_first_calendar == &quot;chores&quot;:
        chores_cal.events.append(cal_event)
    if task_first_calendar == &quot;announcements&quot;:
        ann_cal.events.append(cal_event)


with open(os.path.expanduser(ics_calendar_full_path), 'w') as f:
    f.writelines(general_cal)

with open(os.path.expanduser(ics_calendar_full_path_chores), 'w') as f:
    f.writelines(chores_cal)

with open(os.path.expanduser(ics_calendar_full_path_announcements), 'w') as f:
    f.writelines(ann_cal)

sys.exit(0)

" tabindex="0" role="button">






And the feedback/improved version is given as:

  • Do not mix standard library and third party imports.
  • As you're using Python 3 use function and variable annotations instead of defining types in docstrings. Docstrings are fine but they become outdated too soon. With annotation the advantage is that you can use static analyzers like Mypy to crosscheck.
  • unique_list_of_calendar_tasks = []: unique_list sounds like a set.
  • io.open is not required in Python 3. The builtin open() is fine unless you want to write Python 2/3 compatible code.
  • Do not maintain unique tasks in a global variable, you can easily move it under get_unique_tasks.
  • You seem to be doing a lots of processing related to a task, perhaps create a Task class that encapsulates these methods.

<! -->

from typing import Any, Dict, Iterator

class Task:
def init(self, data: Dict[Any, Any]) -> None:
self.data = data

@property
def id(task) -&gt; str:
    if task.get('due') and task.get('description'):
        return task['due'] + task['description']
    return task['uuid']

@property
def is_valid(self) -&gt; bool:
    if self.data['status'] in invalid_statuses_for_calendar:
        return False

    if not (self.data.get('status') or self.data['status'] in valid_statuses_for_calendar):
        return False
    return True

@property
def first_calendar(self) -&gt; str:
    if self.data.get('tags') is None:
        return ""
    cal = next((s for s in self.data['tags'] if 'cal.' in s), None)
    if cal is None:
        return ""
    return cal.replace("cal.", "")

@property
def start_date_for_event(self) -&gt; str:
    if self.data.get('due'):
        return self.data['due']
    if self.data.get('scheduled'):
        return self.data['scheduled']
    if self.data.get('wait'):
        return self.data['wait']
    else:
        return ""

def __str__(self):
    project = "{} {} {}".format("[", self.data['project'], "] ") if self.data.get('project') else ""
    tags = " [" + ", ".join([k for k in self.data['tags'] if 'cal.' not in k]) + "]" if (self.data.get('tags') and [k for k in
        self.data['tags'] if 'cal.' not in k]) else ""
    return project + self.data['description'] + tags

def __eq__(self, other):
    if isinstance(other, Task):
        return self.id == other.id
    return NotImplemented

def __hash__(self):
    return hash(self.id)

def get_unique_tasks() -> Iterator[Dict[Any, Any]]:
real_lines = []
for line in open(taskwarrior_formatted_data_location, 'r', encoding='utf8'):
li = line.strip()
if li.startswith("{"):
real_lines.append(li)

lines_as_string = "[" + ",".join(real_lines) + "]"
tasks = set()
for row in json.loads(lines_as_string):
    task = Task(row)
    if task not in tasks and task.is_valid:
        yield task
        tasks.add(task)

if name == "main":

general_cal = Calendar(creator="My TaskWarrior Calendar")
chores_cal = Calendar(creator="My TaskWarrior Chores Calendar")
ann_cal = Calendar(creator="My TaskWarrior Announcements Calendar")

for task in get_unique_task():
    event_due = task.start_date_for_event
    if not event_due:
        continue
    cal_event = Event()
    cal_event.begin = event_due
    cal_event.name = str(task)

    task_first_calendar = task.first_calendar

    if task_first_calendar == "":
        general_cal.events.append(cal_event)
    if task_first_calendar == "chores":
        chores_cal.events.append(cal_event)
    if task_first_calendar == "announcements":
        ann_cal.events.append(cal_event)


with open(os.path.expanduser(ics_calendar_full_path), 'w') as f:
    f.writelines(general_cal)

with open(os.path.expanduser(ics_calendar_full_path_chores), 'w') as f:
    f.writelines(chores_cal)

with open(os.path.expanduser(ics_calendar_full_path_announcements), 'w') as f:
    f.writelines(ann_cal)

sys.exit(0)


<clipboard-copy aria-label="Copy" class="ClipboardButton btn js-clipboard-copy m-2 p-0 tooltipped-no-delay" data-copy-feedback="Copied!" data-tooltip-direction="w" value="from typing import Any, Dict, Iterator

class Task:
def init(self, data: Dict[Any, Any]) -> None:
self.data = data

@property
def id(task) -> str:
    if task.get('due') and task.get('description'):
        return task['due'] + task['description']
    return task['uuid']

@property
def is_valid(self) -> bool:
    if self.data['status'] in invalid_statuses_for_calendar:
        return False

    if not (self.data.get('status') or self.data['status'] in valid_statuses_for_calendar):
        return False
    return True

@property
def first_calendar(self) -> str:
    if self.data.get('tags') is None:
        return &quot;&quot;
    cal = next((s for s in self.data['tags'] if 'cal.' in s), None)
    if cal is None:
        return &quot;&quot;
    return cal.replace(&quot;cal.&quot;, &quot;&quot;)

@property
def start_date_for_event(self) -> str:
    if self.data.get('due'):
        return self.data['due']
    if self.data.get('scheduled'):
        return self.data['scheduled']
    if self.data.get('wait'):
        return self.data['wait']
    else:
        return &quot;&quot;

def __str__(self):
    project = &quot;{} {} {}&quot;.format(&quot;[&quot;, self.data['project'], &quot;] &quot;) if self.data.get('project') else &quot;&quot;
    tags = &quot; [&quot; + &quot;, &quot;.join([k for k in self.data['tags'] if 'cal.' not in k]) + &quot;]&quot; if (self.data.get('tags') and [k for k in
        self.data['tags'] if 'cal.' not in k]) else &quot;&quot;
    return project + self.data['description'] + tags

def __eq__(self, other):
    if isinstance(other, Task):
        return self.id == other.id
    return NotImplemented

def __hash__(self):
    return hash(self.id)

def get_unique_tasks() -> Iterator[Dict[Any, Any]]:
real_lines = []
for line in open(taskwarrior_formatted_data_location, 'r', encoding='utf8'):
li = line.strip()
if li.startswith("{"):
real_lines.append(li)

lines_as_string = &quot;[&quot; + &quot;,&quot;.join(real_lines) + &quot;]&quot;
tasks = set()
for row in json.loads(lines_as_string):
    task = Task(row)
    if task not in tasks and task.is_valid:
        yield task
        tasks.add(task)

if name == "main":

general_cal = Calendar(creator=&quot;My TaskWarrior Calendar&quot;)
chores_cal = Calendar(creator=&quot;My TaskWarrior Chores Calendar&quot;)
ann_cal = Calendar(creator=&quot;My TaskWarrior Announcements Calendar&quot;)

for task in get_unique_task():
    event_due = task.start_date_for_event
    if not event_due:
        continue
    cal_event = Event()
    cal_event.begin = event_due
    cal_event.name = str(task)

    task_first_calendar = task.first_calendar

    if task_first_calendar == &quot;&quot;:
        general_cal.events.append(cal_event)
    if task_first_calendar == &quot;chores&quot;:
        chores_cal.events.append(cal_event)
    if task_first_calendar == &quot;announcements&quot;:
        ann_cal.events.append(cal_event)


with open(os.path.expanduser(ics_calendar_full_path), 'w') as f:
    f.writelines(general_cal)

with open(os.path.expanduser(ics_calendar_full_path_chores), 'w') as f:
    f.writelines(chores_cal)

with open(os.path.expanduser(ics_calendar_full_path_announcements), 'w') as f:
    f.writelines(ann_cal)

sys.exit(0)

" tabindex="0" role="button">






  • In the above code Task is now a class whose instances are also hashable, hence you can also store unique tasks in a set.

  • create_task_calendar_description is now the __str__ representation of the task.

  • create_uniqueness has been replaced with id property.

  • is_unique_calendar_task has been removed and instead we use task's id and id_valid properties to identify whether we want to process it or not.

  • We are no longer maintaining unique tasks in a global list, instead we are now using a set in get_unique_tasks to keep track of already processed(and valid) tasks. You could also change the logic here to just store just the task's id in set.

@a-t-0
Copy link
Contributor Author

a-t-0 commented Aug 2, 2021

      <p>fourth</p>

@a-t-0
Copy link
Contributor Author

a-t-0 commented Aug 2, 2021

      <p>fifth</p>

@a-t-0 a-t-0 added calendar Quality help wanted Extra attention is needed labels Aug 2, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
calendar help wanted Extra attention is needed Quality
Projects
None yet
Development

No branches or pull requests

1 participant