mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Split recurring events properly
Previously we moved each VEVENT into its own Item, now we group by UID, if one exists.
This commit is contained in:
parent
b93bcf0ba8
commit
7ce0fb958f
3 changed files with 75 additions and 8 deletions
|
|
@ -9,6 +9,12 @@ Package maintainers and users who have to manually update their installation
|
||||||
may want to subscribe to `GitHub's tag feed
|
may want to subscribe to `GitHub's tag feed
|
||||||
<https://github.com/pimutils/vdirsyncer/tags.atom>`_.
|
<https://github.com/pimutils/vdirsyncer/tags.atom>`_.
|
||||||
|
|
||||||
|
Version 0.9.3
|
||||||
|
=============
|
||||||
|
|
||||||
|
- :storage:`singlefile` and :storage:`http` now handle recurring events
|
||||||
|
properly.
|
||||||
|
|
||||||
Version 0.9.2
|
Version 0.9.2
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from hypothesis import given
|
from hypothesis import given
|
||||||
import hypothesis.strategies as st
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
|
@ -12,7 +14,7 @@ from vdirsyncer.storage.base import Item, normalize_meta_value
|
||||||
from vdirsyncer.utils.compat import iteritems, text_type, urlquote, urlunquote
|
from vdirsyncer.utils.compat import iteritems, text_type, urlquote, urlunquote
|
||||||
|
|
||||||
from .. import EVENT_TEMPLATE, TASK_TEMPLATE, VCARD_TEMPLATE, \
|
from .. import EVENT_TEMPLATE, TASK_TEMPLATE, VCARD_TEMPLATE, \
|
||||||
assert_item_equals, printable_characters_strategy
|
assert_item_equals, normalize_item, printable_characters_strategy
|
||||||
|
|
||||||
|
|
||||||
def get_server_mixin(server_name):
|
def get_server_mixin(server_name):
|
||||||
|
|
@ -285,3 +287,49 @@ class StorageTests(object):
|
||||||
# ownCloud replaces "" with "unnamed"
|
# ownCloud replaces "" with "unnamed"
|
||||||
s.set_meta('displayname', value)
|
s.set_meta('displayname', value)
|
||||||
assert s.get_meta('displayname') == normalize_meta_value(value)
|
assert s.get_meta('displayname') == normalize_meta_value(value)
|
||||||
|
|
||||||
|
def test_recurring_events(self, s, item_type):
|
||||||
|
if item_type != 'VEVENT':
|
||||||
|
pytest.skip('This storage instance doesn\'t support iCalendar.')
|
||||||
|
|
||||||
|
uid = u'abc123'
|
||||||
|
item = Item(textwrap.dedent(u'''
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
VERSION:2.0
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Australia/Sydney:20140325T084000
|
||||||
|
DTEND;TZID=Australia/Sydney:20140325T101000
|
||||||
|
DTSTAMP:20140327T060506Z
|
||||||
|
UID:{uid}
|
||||||
|
RECURRENCE-ID;TZID=Australia/Sydney:20140325T083000
|
||||||
|
CREATED:20131216T033331Z
|
||||||
|
DESCRIPTION:
|
||||||
|
LAST-MODIFIED:20140327T060215Z
|
||||||
|
LOCATION:
|
||||||
|
SEQUENCE:1
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:test Event
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
END:VEVENT
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART;TZID=Australia/Sydney:20140128T083000
|
||||||
|
DTEND;TZID=Australia/Sydney:20140128T100000
|
||||||
|
RRULE:FREQ=WEEKLY;UNTIL=20141208T213000Z;BYDAY=TU
|
||||||
|
DTSTAMP:20140327T060506Z
|
||||||
|
UID:{uid}
|
||||||
|
CREATED:20131216T033331Z
|
||||||
|
DESCRIPTION:
|
||||||
|
LAST-MODIFIED:20140222T101012Z
|
||||||
|
LOCATION:
|
||||||
|
SEQUENCE:0
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:Test event
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
'''.format(uid=uid)).strip())
|
||||||
|
|
||||||
|
href, etag = s.upload(item)
|
||||||
|
|
||||||
|
item2, etag2 = s.get(href)
|
||||||
|
assert normalize_item(item) == normalize_item(item2)
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import hashlib
|
||||||
from itertools import chain, tee
|
from itertools import chain, tee
|
||||||
|
|
||||||
from . import cached_property, uniq
|
from . import cached_property, uniq
|
||||||
from .compat import text_type
|
from .compat import itervalues, text_type
|
||||||
|
|
||||||
|
|
||||||
def _process_properties(*s):
|
def _process_properties(*s):
|
||||||
|
|
@ -114,17 +114,24 @@ def hash_item(text):
|
||||||
def split_collection(text):
|
def split_collection(text):
|
||||||
assert isinstance(text, text_type)
|
assert isinstance(text, text_type)
|
||||||
inline = []
|
inline = []
|
||||||
items = []
|
items = {} # uid => item
|
||||||
|
ungrouped_items = []
|
||||||
|
|
||||||
def inner(item, main):
|
def inner(item, main):
|
||||||
if item.name == u'VTIMEZONE':
|
if item.name == u'VTIMEZONE':
|
||||||
inline.append(item)
|
inline.append(item)
|
||||||
elif item.name == u'VCARD':
|
elif item.name == u'VCARD':
|
||||||
items.append(item)
|
ungrouped_items.append(item)
|
||||||
elif item.name in (u'VTODO', u'VEVENT', u'VJOURNAL'):
|
elif item.name in (u'VTODO', u'VEVENT', u'VJOURNAL'):
|
||||||
items.append(_Component(main.name,
|
uid = item.get(u'UID', u'')
|
||||||
main.props[:],
|
wrapper = _Component(main.name, main.props[:], [])
|
||||||
[item]))
|
|
||||||
|
if uid.strip():
|
||||||
|
wrapper = items.setdefault(uid, wrapper)
|
||||||
|
else:
|
||||||
|
ungrouped_items.append(wrapper)
|
||||||
|
|
||||||
|
wrapper.subcomponents.append(item)
|
||||||
elif item.name in (u'VCALENDAR', u'VADDRESSBOOK'):
|
elif item.name in (u'VCALENDAR', u'VADDRESSBOOK'):
|
||||||
for subitem in item.subcomponents:
|
for subitem in item.subcomponents:
|
||||||
inner(subitem, item)
|
inner(subitem, item)
|
||||||
|
|
@ -135,7 +142,7 @@ def split_collection(text):
|
||||||
for main in _Component.parse(text, multiple=True):
|
for main in _Component.parse(text, multiple=True):
|
||||||
inner(main, main)
|
inner(main, main)
|
||||||
|
|
||||||
for item in items:
|
for item in chain(itervalues(items), ungrouped_items):
|
||||||
item.subcomponents.extend(inline)
|
item.subcomponents.extend(inline)
|
||||||
yield u'\r\n'.join(item.dump_lines())
|
yield u'\r\n'.join(item.dump_lines())
|
||||||
|
|
||||||
|
|
@ -316,3 +323,9 @@ class _Component(object):
|
||||||
break
|
break
|
||||||
|
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue