Use vobject
This commit is contained in:
parent
b495bfa59f
commit
73d39ea572
@ -31,6 +31,8 @@ from contextlib import contextmanager
|
|||||||
from random import randint
|
from random import randint
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import vobject
|
||||||
|
|
||||||
|
|
||||||
def serialize(tag, headers=(), items=()):
|
def serialize(tag, headers=(), items=()):
|
||||||
"""Return a text corresponding to given collection ``tag``.
|
"""Return a text corresponding to given collection ``tag``.
|
||||||
@ -41,14 +43,15 @@ def serialize(tag, headers=(), items=()):
|
|||||||
"""
|
"""
|
||||||
items = sorted(items, key=lambda x: x.name)
|
items = sorted(items, key=lambda x: x.name)
|
||||||
if tag == "VADDRESSBOOK":
|
if tag == "VADDRESSBOOK":
|
||||||
lines = [item.text for item in items]
|
lines = [item.text.strip() for item in items]
|
||||||
else:
|
else:
|
||||||
lines = ["BEGIN:%s" % tag]
|
lines = ["BEGIN:%s" % tag]
|
||||||
for part in (headers, items):
|
for part in (headers, items):
|
||||||
if part:
|
if part:
|
||||||
lines.append("\n".join(item.text for item in part))
|
lines.append("\r\n".join(item.text.strip() for item in part))
|
||||||
lines.append("END:%s\n" % tag)
|
lines.append("END:%s" % tag)
|
||||||
return "\n".join(lines)
|
lines.append("")
|
||||||
|
return "\r\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
def sanitize_path(path):
|
def sanitize_path(path):
|
||||||
@ -90,9 +93,14 @@ class Item(object):
|
|||||||
"""Internal iCal item."""
|
"""Internal iCal item."""
|
||||||
def __init__(self, text, name=None):
|
def __init__(self, text, name=None):
|
||||||
"""Initialize object from ``text`` and different ``kwargs``."""
|
"""Initialize object from ``text`` and different ``kwargs``."""
|
||||||
self.text = text
|
self.component = vobject.readOne(text)
|
||||||
self._name = name
|
self._name = name
|
||||||
|
|
||||||
|
if not self.component.name:
|
||||||
|
# Header
|
||||||
|
self._name = next(self.component.lines()).name.lower()
|
||||||
|
return
|
||||||
|
|
||||||
# We must synchronize the name in the text and in the object.
|
# We must synchronize the name in the text and in the object.
|
||||||
# An item must have a name, determined in order by:
|
# An item must have a name, determined in order by:
|
||||||
#
|
#
|
||||||
@ -101,31 +109,20 @@ class Item(object):
|
|||||||
# - the ``UID`` iCal property (for Events, Todos, Journals)
|
# - the ``UID`` iCal property (for Events, Todos, Journals)
|
||||||
# - the ``TZID`` iCal property (for Timezones)
|
# - the ``TZID`` iCal property (for Timezones)
|
||||||
if not self._name:
|
if not self._name:
|
||||||
for line in unfold(self.text):
|
for line in self.component.lines():
|
||||||
if line.startswith("X-RADICALE-NAME:"):
|
if line.name in ("X-RADICALE-NAME", "UID", "TZID"):
|
||||||
self._name = line.replace("X-RADICALE-NAME:", "").strip()
|
self._name = line.value
|
||||||
break
|
if line.name == "X-RADICALE-NAME":
|
||||||
elif line.startswith("UID:"):
|
break
|
||||||
self._name = line.replace("UID:", "").strip()
|
|
||||||
# Do not break, a ``X-RADICALE-NAME`` can appear next
|
|
||||||
elif line.startswith("TZID:"):
|
|
||||||
self._name = line.replace("TZID:", "").strip()
|
|
||||||
# Do not break, a ``X-RADICALE-NAME`` can appear next
|
|
||||||
|
|
||||||
if self._name:
|
if self._name:
|
||||||
self._name = clean_name(self._name)
|
self._name = clean_name(self._name)
|
||||||
if "\nX-RADICALE-NAME:" in text:
|
|
||||||
for line in unfold(self.text):
|
|
||||||
if line.startswith("X-RADICALE-NAME:"):
|
|
||||||
self.text = self.text.replace(
|
|
||||||
line, "X-RADICALE-NAME:%s" % self._name)
|
|
||||||
else:
|
|
||||||
self.text = self.text.replace(
|
|
||||||
"\nEND:V", "\nX-RADICALE-NAME:%s\nEND:V" % self._name)
|
|
||||||
else:
|
else:
|
||||||
self._name = uuid4().hex
|
self._name = uuid4().hex
|
||||||
self.text = self.text.replace(
|
|
||||||
"\nEND:V", "\nX-RADICALE-NAME:%s\nEND:V" % self._name)
|
if not hasattr(self.component, "x_radicale_name"):
|
||||||
|
self.component.add("X-RADICALE-NAME")
|
||||||
|
self.component.x_radicale_name.value = self._name
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.text)
|
return hash(self.text)
|
||||||
@ -153,6 +150,11 @@ class Item(object):
|
|||||||
"""
|
"""
|
||||||
return self._name
|
return self._name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def text(self):
|
||||||
|
"""Item serialized text."""
|
||||||
|
return self.component.serialize()
|
||||||
|
|
||||||
|
|
||||||
class Header(Item):
|
class Header(Item):
|
||||||
"""Internal header class."""
|
"""Internal header class."""
|
||||||
@ -335,35 +337,21 @@ class Collection(object):
|
|||||||
Return a dict of items.
|
Return a dict of items.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
item_tags = {}
|
item_tags = {item_type.tag: item_type for item_type in item_types}
|
||||||
for item_type in item_types:
|
|
||||||
item_tags[item_type.tag] = item_type
|
|
||||||
|
|
||||||
items = {}
|
items = {}
|
||||||
|
root = next(vobject.readComponents(text))
|
||||||
lines = unfold(text)
|
components = (
|
||||||
in_item = False
|
root.components() if root.name in ("VADDRESSBOOK", "VCALENDAR")
|
||||||
|
else (root,))
|
||||||
for line in lines:
|
for component in components:
|
||||||
if line.startswith("BEGIN:") and not in_item:
|
item_name = None if component.name == "VTIMEZONE" else name
|
||||||
item_tag = line.replace("BEGIN:", "").strip()
|
item_type = item_tags[component.name]
|
||||||
if item_tag in item_tags:
|
item = item_type(component.serialize(), item_name)
|
||||||
in_item = True
|
if item.name in items:
|
||||||
item_lines = []
|
text = "\r\n".join((item.text, items[item.name].text))
|
||||||
|
items[item.name] = item_type(text, item.name)
|
||||||
if in_item:
|
else:
|
||||||
item_lines.append(line)
|
items[item.name] = item
|
||||||
if line.startswith("END:%s" % item_tag):
|
|
||||||
in_item = False
|
|
||||||
item_type = item_tags[item_tag]
|
|
||||||
item_text = "\n".join(item_lines)
|
|
||||||
item_name = None if item_tag == "VTIMEZONE" else name
|
|
||||||
item = item_type(item_text, item_name)
|
|
||||||
if item.name in items:
|
|
||||||
text = "\n".join((item.text, items[item.name].text))
|
|
||||||
items[item.name] = item_type(text, item.name)
|
|
||||||
else:
|
|
||||||
items[item.name] = item
|
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
@ -140,7 +140,6 @@ class Collection(ical.Collection):
|
|||||||
ical.Timezone, ical.Event, ical.Todo, ical.Journal, ical.Card)
|
ical.Timezone, ical.Event, ical.Todo, ical.Journal, ical.Card)
|
||||||
for name, component in self._parse(text, item_types).items():
|
for name, component in self._parse(text, item_types).items():
|
||||||
if not is_safe_filesystem_path_component(name):
|
if not is_safe_filesystem_path_component(name):
|
||||||
# TODO: Timezones with slashes can't be saved
|
|
||||||
log.LOGGER.debug(
|
log.LOGGER.debug(
|
||||||
"Can't tranlate name safely to filesystem, "
|
"Can't tranlate name safely to filesystem, "
|
||||||
"skipping component: %s", name)
|
"skipping component: %s", name)
|
||||||
|
1
setup.py
1
setup.py
@ -56,6 +56,7 @@ setup(
|
|||||||
packages=["radicale"],
|
packages=["radicale"],
|
||||||
provides=["radicale"],
|
provides=["radicale"],
|
||||||
scripts=["bin/radicale"],
|
scripts=["bin/radicale"],
|
||||||
|
install_requires=["vobject"],
|
||||||
keywords=["calendar", "addressbook", "CalDAV", "CardDAV"],
|
keywords=["calendar", "addressbook", "CalDAV", "CardDAV"],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Development Status :: 5 - Production/Stable",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user