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

WiP: Support for alarms #221

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions caldav/lib/vcal.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ def fix(event):

## sorry for being english-language-euro-centric ... fits rather perfectly as default language for me :-)
def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
"""
I somehow feel this fits more into the icalendar library than here
"""Creates some icalendar based on properties given as parameters.
It basically creates an icalendar object with all the boilerplate,
some sensible defaults, the properties given and returns it as a
string.

TODO: timezones not supported so far
"""
ical_fragment = to_normal_str(ical_fragment)
if not ical_fragment or not re.search("^BEGIN:V", ical_fragment, re.MULTILINE):
Expand All @@ -105,18 +109,31 @@ def create_ical(ical_fragment=None, objtype=None, language="en_DK", **props):
my_instance = icalendar.Calendar.from_ical(ical_fragment)
component = my_instance.subcomponents[0]
ical_fragment = None
alarm = {}
for prop in props:
if props[prop] is not None:
if prop in ("child", "parent"):
for value in props[prop]:
component.add(
"related-to", props[prop], parameters={"rel-type": prop.upper()}
)
elif prop.startswith("alarm_"):
alarm[prop[6:]] = props[prop]
else:
component.add(prop, props[prop])
if alarm:
add_alarm(my_instance, alarm)
ret = to_normal_str(my_instance.to_ical())
if ical_fragment and ical_fragment.strip():
ret = re.sub(
"^END:V", ical_fragment.strip() + "\nEND:V", ret, flags=re.MULTILINE
)
return ret


def add_alarm(ical, alarm):
ia = icalendar.Alarm()
for prop in alarm:
ia.add(prop, alarm[prop])
ical.subcomponents[0].add_component(ia)
return ical
16 changes: 12 additions & 4 deletions caldav/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -704,7 +704,8 @@ def save_event(self, ical=None, no_overwrite=False, no_create=False, **ical_data
* ical - ical object (text)
* no_overwrite - existing calendar objects should not be overwritten
* no_create - don't create a new object, existing calendar objects should be updated
* ical_data - passed to lib.vcal.create_ical
* dt_start, dt_end, summary, etc - properties to be inserted into the icalendar object
* alarm_trigger, alarm_action, alarm_attach, etc - when given, one alarm will be added
"""
e = Event(
self.client,
Expand Down Expand Up @@ -1106,6 +1107,8 @@ def build_search_xml_query(
expand=None,
start=None,
end=None,
alarm_start=None,
alarm_end=None,
**kwargs
):
"""This method will produce a caldav search query as an etree object.
Expand Down Expand Up @@ -1164,6 +1167,11 @@ def build_search_xml_query(
if start or end:
filters.append(cdav.TimeRange(start, end))

if alarm_start or alarm_end:
filters.append(
cdav.CompFilter("VALARM") + cdav.TimeRange(alarm_start, alarm_end)
)

if todo is not None:
if not todo:
raise NotImplementedError()
Expand Down Expand Up @@ -1906,7 +1914,7 @@ def copy(self, keep_uid=False, new_parent=None):
id=self.id if keep_uid else str(uuid.uuid1()),
)
if new_parent or not keep_uid:
obj.url = obj.generate_url()
obj.url = obj._generate_url()
else:
obj.url = self.url
return obj
Expand Down Expand Up @@ -1963,7 +1971,7 @@ def _find_id_path(self, id=None, path=None):
error.assert_(x.get("UID", None) == self.id)

if path is None:
path = self.generate_url()
path = self._generate_url()
else:
path = self.parent.url.join(path)

Expand Down Expand Up @@ -1992,7 +2000,7 @@ def _create(self, id=None, path=None, retry_on_failure=True):
self._find_id_path(id=id, path=path)
self._put()

def generate_url(self):
def _generate_url(self):
## See https://github.com/python-caldav/caldav/issues/143 for the rationale behind double-quoting slashes
## TODO: should try to wrap my head around issues that arises when id contains weird characters. maybe it's
## better to generate a new uuid here, particularly if id is in some unexpected format.
Expand Down
34 changes: 34 additions & 0 deletions tests/test_caldav.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from collections import namedtuple
from datetime import date
from datetime import datetime
from datetime import timedelta

import pytest
import requests
Expand Down Expand Up @@ -766,6 +767,39 @@ def testCreateEvent(self):
events = c.events()
assert len(events) == len(existing_events) + 2

def testAlarm(self):
## Ref https://github.com/python-caldav/caldav/issues/132
c = self._fixCalendar()
ev = c.save_event(
dtstart=datetime(2015, 10, 10, 8, 7, 6),
summary="This is a test event",
dtend=datetime(2016, 10, 10, 9, 8, 7),
alarm_trigger=timedelta(minutes=-15),
alarm_action="AUDIO",
)

## Search for the alarm (procrastinated - see https://github.com/python-caldav/caldav/issues/132)
assert (
len(
c.search(
event=True,
alarm_start=datetime(2015, 10, 10, 8, 1),
alarm_end=datetime(2015, 10, 10, 8, 7),
)
)
== 0
)
assert (
len(
c.search(
event=True,
alarm_start=datetime(2015, 10, 10, 7, 44),
alarm_end=datetime(2015, 10, 10, 8, 7),
)
)
== 1
)

def testCalendarByFullURL(self):
"""
ref private email, passing a full URL as cal_id works in 0.5.0 but
Expand Down