Skip to content

Commit

Permalink
Support for random uids in import
Browse files Browse the repository at this point in the history
  • Loading branch information
geier committed Oct 6, 2016
1 parent 8ca650d commit 9334546
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 17 deletions.
15 changes: 7 additions & 8 deletions khal/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,26 +526,25 @@ def import_ics(collection, conf, ics, batch=False, random_uid=False, format=None
:param batch: setting this to True will insert without asking for approval,
even when an event with the same uid already exists
:type batch: bool
:param random_uid: whether to assign a random UID to imported events or not
:type random_uid: bool
:param format: the format string to print events with
:type format: str
"""
if format is None:
format = conf['view']['event_format']

vevents = utils.split_ics(ics)

vevents = utils.split_ics(ics, random_uid)
for vevent in vevents:
import_event(
vevent, collection, conf['locale'], batch, random_uid, format, env,
)
import_event(vevent, collection, conf['locale'], batch, format, env)


def import_event(vevent, collection, locale, batch, random_uid, format=None, env=None):
def import_event(vevent, collection, locale, batch, format=None, env=None):
"""import one event into collection, let user choose the collection
:type vevent: list of vevents, which can be more than one VEVENT, i.e., the
same UID, i.e., one "master" event and (optionally) 1+ RECURRENCE-ID events
:type vevent: list(str)
"""
# TODO re-enable random_uid
# print all sub-events
if not batch:
for item in icalendar.Calendar.from_ical(vevent).walk():
Expand Down
15 changes: 12 additions & 3 deletions khal/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -593,12 +593,14 @@ def new_event(locale, dtstart=None, dtend=None, summary=None, timezone=None,
return event


def split_ics(ics):
def split_ics(ics, random_uid=False):
"""split an ics string into several according to VEVENT's UIDs
and sort the right VTIMEZONEs accordingly
ignores all other ics components
:type ics: str
:param random_uid: assign random uids to all events
:type random_uid: bool
:rtype list:
"""
cal = icalendar.Calendar.from_ical(ics)
Expand All @@ -610,23 +612,30 @@ def split_ics(ics):
events_grouped[item['UID']].append(item)
else:
continue
return [ics_from_list(events, tzs) for uid, events in sorted(events_grouped.items())]
return [ics_from_list(events, tzs, random_uid) for uid, events in sorted(events_grouped.items())]


def ics_from_list(events, tzs):
def ics_from_list(events, tzs, random_uid=False):
"""convert an iterable of icalendar.Events to an icalendar.Calendar
:params events: list of events all with the same uid
:type events: list(icalendar.cal.Event)
:param random_uid: assign random uids to all events
:type random_uid: bool
:param tzs: collection of timezones
:type tzs: dict(icalendar.cal.Vtimzone
"""
calendar = icalendar.Calendar()
calendar.add('version', '2.0')
calendar.add('prodid', '-//CALENDARSERVER.ORG//NONSGML Version 1//EN')

if random_uid:
new_uid = generate_random_uid()

needed_tz, missing_tz = set(), set()
for sub_event in events:
if random_uid:
sub_event['UID'] = new_uid
# icalendar round-trip converts `TZID=a b` to `TZID="a b"` investigate, file bug XXX
for prop in ['DTSTART', 'DTEND', 'DUE', 'EXDATE', 'RDATE', 'RECURRENCE-ID', 'DUE']:
if isinstance(sub_event.get(prop), list):
Expand Down
43 changes: 37 additions & 6 deletions tests/utils_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from datetime import date, datetime, time, timedelta
from collections import OrderedDict
import textwrap
import random

import icalendar
import pytz
from freezegun import freeze_time

Expand Down Expand Up @@ -80,6 +82,11 @@ def _replace_uid(event):
return event


def _get_TZIDs(lines):
"""from a list of strings, get all unique strings that start with TZID"""
return sorted((line for line in lines if line.startswith('TZID')))


def test_normalize_component():
assert normalize_component(textwrap.dedent("""
BEGIN:VEVENT
Expand Down Expand Up @@ -513,13 +520,37 @@ def test_split_ics():
vevents0 = vevents[0].split('\r\n')
vevents1 = vevents[1].split('\r\n')

part0 = _get_text('part1').split('\n')
part1 = _get_text('part0').split('\n')
part0 = _get_text('part0').split('\n')
part1 = _get_text('part1').split('\n')

assert _get_TZIDs(vevents0) == _get_TZIDs(part0)
assert _get_TZIDs(vevents1) == _get_TZIDs(part1)

assert sorted(vevents0) == sorted(part0)
assert sorted(vevents1) == sorted(part1)


def test_split_ics_random_uid():
random.seed(123)
cal = _get_text('cal_lots_of_timezones')
vevents = utils.split_ics(cal, random_uid=True)

part0 = _get_text('part0').split('\n')
part1 = _get_text('part1').split('\n')

for item in icalendar.Calendar.from_ical(vevents[0]).walk():
if item.name == 'VEVENT':
assert item['UID'] == 'DRF0RGCY89VVDKIV9VPKA1FYEAU2GCFJIBS1'
for item in icalendar.Calendar.from_ical(vevents[1]).walk():
if item.name == 'VEVENT':
assert item['UID'] == '4Q4CTV74N7UAZ618570X6CLF5QKVV9ZE3YVB'

# after replacing the UIDs, everything should be as above
vevents0 = vevents[0].replace('DRF0RGCY89VVDKIV9VPKA1FYEAU2GCFJIBS1', '123').split('\r\n')
vevents1 = vevents[1].replace('4Q4CTV74N7UAZ618570X6CLF5QKVV9ZE3YVB', 'abcde').split('\r\n')

assert sorted([line for line in vevents1 if line.startswith('TZID')]) == \
sorted([line for line in part1 if line.startswith('TZID')])
assert sorted([line for line in vevents0 if line.startswith('TZID')]) == \
sorted([line for line in part0 if line.startswith('TZID')])
assert _get_TZIDs(vevents0) == _get_TZIDs(part0)
assert _get_TZIDs(vevents1) == _get_TZIDs(part1)

assert sorted(vevents0) == sorted(part0)
assert sorted(vevents1) == sorted(part1)

0 comments on commit 9334546

Please sign in to comment.