mirror of
https://github.com/samsonjs/vdirsyncer.git
synced 2026-03-25 08:55:50 +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()
|
||||
if u'PRODID' not in line and u'VERSION' not in line)
|
||||
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 utils
|
||||
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
|
||||
from vdirsyncer.utils.vobject import Item # noqa
|
||||
|
||||
|
||||
class Storage(object):
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ from .compat import urlparse, get_raw_input
|
|||
|
||||
|
||||
logger = log.get(__name__)
|
||||
_missing = object()
|
||||
|
||||
|
||||
try:
|
||||
|
|
@ -288,3 +289,25 @@ def checkfile(path, create=False):
|
|||
'True in your configuration to automatically '
|
||||
'create it, or create it '
|
||||
'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.caselessdict
|
||||
|
||||
from . import cached_property
|
||||
from .compat import text_type, itervalues
|
||||
|
||||
|
||||
|
|
@ -47,13 +48,71 @@ ICALENDAR_ORIGINAL_ORDER_SUPPORT = \
|
|||
hasattr(icalendar.caselessdict.CaselessDict, '__reversed__')
|
||||
|
||||
|
||||
def normalize_item(text, ignore_props=IGNORE_PROPS, use_icalendar=True):
|
||||
try:
|
||||
if not use_icalendar:
|
||||
raise Exception()
|
||||
lines = to_unicode_lines(icalendar.cal.Component.from_ical(text))
|
||||
except Exception:
|
||||
lines = sorted(text.splitlines())
|
||||
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:
|
||||
return icalendar.cal.Component.from_ical(self.raw)
|
||||
except Exception:
|
||||
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()
|
||||
for line in lines
|
||||
|
|
|
|||
Loading…
Reference in a new issue