mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-04-27 14:57:41 +00:00
Merge pull request #75 from untitaker/multiline_uids
Multiline uids Fix #74
This commit is contained in:
commit
967540fe62
4 changed files with 141 additions and 43 deletions
|
|
@ -85,3 +85,54 @@ def test_hash_item():
|
||||||
b = u'\n'.join(line for line in a.splitlines()
|
b = u'\n'.join(line for line in a.splitlines()
|
||||||
if u'PRODID' not in line and u'VERSION' not in line)
|
if u'PRODID' not in line and u'VERSION' not in line)
|
||||||
assert vobject.hash_item(a) == vobject.hash_item(b)
|
assert vobject.hash_item(a) == vobject.hash_item(b)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiline_uid():
|
||||||
|
a = (u'BEGIN:FOO\r\n'
|
||||||
|
u'UID:123456789abcd\r\n'
|
||||||
|
u' efgh\r\n'
|
||||||
|
u'END:FOO\r\n')
|
||||||
|
assert vobject.Item(a).uid == u'123456789abcdefgh'
|
||||||
|
|
||||||
|
|
||||||
|
def test_multiline_uid_complex():
|
||||||
|
a = u'''
|
||||||
|
BEGIN:VCALENDAR
|
||||||
|
BEGIN:VTIMEZONE
|
||||||
|
TZID:Europe/Rome
|
||||||
|
X-LIC-LOCATION:Europe/Rome
|
||||||
|
BEGIN:DAYLIGHT
|
||||||
|
TZOFFSETFROM:+0100
|
||||||
|
TZOFFSETTO:+0200
|
||||||
|
TZNAME:CEST
|
||||||
|
DTSTART:19700329T020000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
|
||||||
|
END:DAYLIGHT
|
||||||
|
BEGIN:STANDARD
|
||||||
|
TZOFFSETFROM:+0200
|
||||||
|
TZOFFSETTO:+0100
|
||||||
|
TZNAME:CET
|
||||||
|
DTSTART:19701025T030000
|
||||||
|
RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
|
||||||
|
END:STANDARD
|
||||||
|
END:VTIMEZONE
|
||||||
|
BEGIN:VEVENT
|
||||||
|
DTSTART:20140124T133000Z
|
||||||
|
DTEND:20140124T143000Z
|
||||||
|
DTSTAMP:20140612T090652Z
|
||||||
|
UID:040000008200E00074C5B7101A82E0080000000050AAABEEF50DCF0100000000000000
|
||||||
|
001000000062548482FA830A46B9EA62114AC9F0EF
|
||||||
|
CREATED:20140110T102231Z
|
||||||
|
DESCRIPTION:Test.
|
||||||
|
LAST-MODIFIED:20140123T095221Z
|
||||||
|
LOCATION:25.12.01.51
|
||||||
|
SEQUENCE:0
|
||||||
|
STATUS:CONFIRMED
|
||||||
|
SUMMARY:Präsentation
|
||||||
|
TRANSP:OPAQUE
|
||||||
|
END:VEVENT
|
||||||
|
END:VCALENDAR
|
||||||
|
'''.strip()
|
||||||
|
assert vobject.Item(a).uid == (u'040000008200E00074C5B7101A82E008000000005'
|
||||||
|
u'0AAABEEF50DCF0100000000000000001000000062'
|
||||||
|
u'548482FA830A46B9EA62114AC9F0EF')
|
||||||
|
|
|
||||||
|
|
@ -9,42 +9,7 @@
|
||||||
|
|
||||||
|
|
||||||
from .. import exceptions
|
from .. import exceptions
|
||||||
from .. import utils
|
from vdirsyncer.utils.vobject import Item # noqa
|
||||||
from ..utils.compat import text_type
|
|
||||||
|
|
||||||
|
|
||||||
class Item(object):
|
|
||||||
|
|
||||||
'''should-be-immutable wrapper class for VCALENDAR (VEVENT, VTODO) and
|
|
||||||
VCARD'''
|
|
||||||
|
|
||||||
uid = None
|
|
||||||
'''Global identifier of the item, across storages, doesn't change after a
|
|
||||||
modification of the item.'''
|
|
||||||
|
|
||||||
raw = None
|
|
||||||
'''Raw content of the item, which vdirsyncer doesn't validate in any
|
|
||||||
way.'''
|
|
||||||
|
|
||||||
hash = None
|
|
||||||
'''Hash of self.raw, used for etags.'''
|
|
||||||
|
|
||||||
ident = None
|
|
||||||
'''Used for generating hrefs and matching up items during synchronization.
|
|
||||||
This is either the UID or the hash of the item's content.'''
|
|
||||||
|
|
||||||
def __init__(self, raw):
|
|
||||||
assert isinstance(raw, text_type)
|
|
||||||
|
|
||||||
for line in raw.splitlines():
|
|
||||||
if line.startswith(u'UID:'):
|
|
||||||
uid = line[4:].strip()
|
|
||||||
if uid:
|
|
||||||
self.uid = uid
|
|
||||||
|
|
||||||
self.raw = raw
|
|
||||||
self.hash = utils.vobject.hash_item(raw)
|
|
||||||
self.ident = self.uid or self.hash
|
|
||||||
|
|
||||||
|
|
||||||
class Storage(object):
|
class Storage(object):
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ from .compat import urlparse, get_raw_input
|
||||||
|
|
||||||
|
|
||||||
logger = log.get(__name__)
|
logger = log.get(__name__)
|
||||||
|
_missing = object()
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -288,3 +289,25 @@ def checkfile(path, create=False):
|
||||||
'True in your configuration to automatically '
|
'True in your configuration to automatically '
|
||||||
'create it, or create it '
|
'create it, or create it '
|
||||||
'yourself.'.format(path))
|
'yourself.'.format(path))
|
||||||
|
|
||||||
|
|
||||||
|
class cached_property(object):
|
||||||
|
'''
|
||||||
|
Copied from Werkzeug.
|
||||||
|
Copyright 2007-2014 Armin Ronacher
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, func, name=None, doc=None):
|
||||||
|
self.__name__ = name or func.__name__
|
||||||
|
self.__module__ = func.__module__
|
||||||
|
self.__doc__ = doc or func.__doc__
|
||||||
|
self.func = func
|
||||||
|
|
||||||
|
def __get__(self, obj, type=None):
|
||||||
|
if obj is None:
|
||||||
|
return self
|
||||||
|
value = obj.__dict__.get(self.__name__, _missing)
|
||||||
|
if value is _missing:
|
||||||
|
value = self.func(obj)
|
||||||
|
obj.__dict__[self.__name__] = value
|
||||||
|
return value
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import icalendar.cal
|
||||||
import icalendar.parser
|
import icalendar.parser
|
||||||
import icalendar.caselessdict
|
import icalendar.caselessdict
|
||||||
|
|
||||||
|
from . import cached_property
|
||||||
from .compat import text_type, itervalues
|
from .compat import text_type, itervalues
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -47,13 +48,71 @@ ICALENDAR_ORIGINAL_ORDER_SUPPORT = \
|
||||||
hasattr(icalendar.caselessdict.CaselessDict, '__reversed__')
|
hasattr(icalendar.caselessdict.CaselessDict, '__reversed__')
|
||||||
|
|
||||||
|
|
||||||
def normalize_item(text, ignore_props=IGNORE_PROPS, use_icalendar=True):
|
class Item(object):
|
||||||
|
|
||||||
|
'''should-be-immutable wrapper class for VCALENDAR (VEVENT, VTODO) and
|
||||||
|
VCARD'''
|
||||||
|
|
||||||
|
def __init__(self, raw):
|
||||||
|
assert isinstance(raw, text_type)
|
||||||
|
|
||||||
|
self._raw = raw
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def raw(self):
|
||||||
|
'''Raw content of the item, which vdirsyncer doesn't validate in any
|
||||||
|
way.'''
|
||||||
|
return self._raw
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def uid(self):
|
||||||
|
'''Global identifier of the item, across storages, doesn't change after
|
||||||
|
a modification of the item.'''
|
||||||
|
stack = [self.parsed]
|
||||||
|
while stack:
|
||||||
|
component = stack.pop()
|
||||||
|
if component is None:
|
||||||
|
continue
|
||||||
|
uid = component.get('UID', None)
|
||||||
|
if uid:
|
||||||
|
return uid
|
||||||
|
stack.extend(component.subcomponents)
|
||||||
|
|
||||||
|
for line in self.raw.splitlines():
|
||||||
|
if line.startswith(u'UID:'):
|
||||||
|
uid = line[4:].strip()
|
||||||
|
if uid:
|
||||||
|
return uid
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def hash(self):
|
||||||
|
'''Hash of self.raw, used for etags.'''
|
||||||
|
return hash_item(self.raw)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def ident(self):
|
||||||
|
'''Used for generating hrefs and matching up items during
|
||||||
|
synchronization. This is either the UID or the hash of the item's
|
||||||
|
content.'''
|
||||||
|
return self.uid or self.hash
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def parsed(self):
|
||||||
try:
|
try:
|
||||||
if not use_icalendar:
|
return icalendar.cal.Component.from_ical(self.raw)
|
||||||
raise Exception()
|
|
||||||
lines = to_unicode_lines(icalendar.cal.Component.from_ical(text))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
lines = sorted(text.splitlines())
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_item(item, ignore_props=IGNORE_PROPS, use_icalendar=True):
|
||||||
|
if not isinstance(item, Item):
|
||||||
|
item = Item(item)
|
||||||
|
if use_icalendar and item.parsed is not None:
|
||||||
|
# We have to explicitly check "is not None" here because VCALENDARS
|
||||||
|
# with only subcomponents and no own properties are also false-ish.
|
||||||
|
lines = to_unicode_lines(item.parsed)
|
||||||
|
else:
|
||||||
|
lines = sorted(item.raw.splitlines())
|
||||||
|
|
||||||
return u'\r\n'.join(line.strip()
|
return u'\r\n'.join(line.strip()
|
||||||
for line in lines
|
for line in lines
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue