Parcourir la source

Merge ical/support/calendar modules.

Guillaume Ayoub il y a 16 ans
Parent
commit
9a07ec71d3
8 fichiers modifiés avec 218 ajouts et 355 suppressions
  1. 22 13
      radicale/__init__.py
  2. 177 61
      radicale/calendar.py
  3. 14 18
      radicale/config.py
  4. 0 98
      radicale/ical.py
  5. 0 32
      radicale/support/__init__.py
  6. 0 128
      radicale/support/plain.py
  7. 4 4
      radicale/xmlutils.py
  8. 1 1
      setup.py

+ 22 - 13
radicale/__init__.py

@@ -33,15 +33,19 @@ should have been included in this package.
 
 """
 
+import os
 import base64
 import socket
+# Manage Python2/3 different modules
+# pylint: disable-msg=F0401
 try:
     from http import client, server
 except ImportError:
     import httplib as client
     import BaseHTTPServer as server
+# pylint: enable-msg=F0401
 
-from radicale import acl, calendar, config, support, xmlutils
+from radicale import acl, calendar, config, xmlutils
 
 
 def _check(request, function):
@@ -77,7 +81,9 @@ class HTTPSServer(HTTPServer):
     def __init__(self, address, handler):
         """Create server by wrapping HTTP socket in an SSL socket."""
         # Fails with Python 2.5, import if needed
+        # pylint: disable-msg=F0401
         import ssl
+        # pylint: enable-msg=F0401
 
         HTTPServer.__init__(self, address, handler)
         self.socket = ssl.wrap_socket(
@@ -98,14 +104,15 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     check_rights = lambda function: lambda request: _check(request, function)
 
     @property
-    def calendar(self):
+    def _calendar(self):
         """The ``calendar.Calendar`` object corresponding to the given path."""
-        path = self.path.strip("/").split("/")
-        if len(path) >= 2:
-            cal = "%s/%s" % (path[0], path[1])
-            return calendar.Calendar("radicale", cal)
+        # ``normpath`` should clean malformed and malicious request paths
+        attributes = os.path.normpath(self.path.strip("/")).split("/")
+        if len(attributes) >= 2:
+            path = "%s/%s" % (attributes[0], attributes[1])
+            return calendar.Calendar(path)
 
-    def decode(self, text):
+    def _decode(self, text):
         """Try to decode text according to various parameters."""
         # List of charsets to try
         charsets = []
@@ -134,7 +141,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     @check_rights
     def do_GET(self):
         """Manage GET request."""
-        answer = self.calendar.vcalendar.encode(self._encoding)
+        answer = self._calendar.read().encode(self._encoding)
 
         self.send_response(client.OK)
         self.send_header("Content-Length", len(answer))
@@ -145,7 +152,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     def do_DELETE(self):
         """Manage DELETE request."""
         obj = self.headers.get("If-Match", None)
-        answer = xmlutils.delete(obj, self.calendar, self.path)
+        answer = xmlutils.delete(obj, self._calendar, self.path)
 
         self.send_response(client.NO_CONTENT)
         self.send_header("Content-Length", len(answer))
@@ -162,7 +169,7 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     def do_PROPFIND(self):
         """Manage PROPFIND request."""
         xml_request = self.rfile.read(int(self.headers["Content-Length"]))
-        answer = xmlutils.propfind(xml_request, self.calendar, self.path)
+        answer = xmlutils.propfind(xml_request, self._calendar, self.path)
 
         self.send_response(client.MULTI_STATUS)
         self.send_header("DAV", "1, calendar-access")
@@ -173,10 +180,10 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     @check_rights
     def do_PUT(self):
         """Manage PUT request."""
-        ical_request = self.decode(
+        ical_request = self._decode(
             self.rfile.read(int(self.headers["Content-Length"])))
         obj = self.headers.get("If-Match", None)
-        xmlutils.put(ical_request, self.calendar, self.path, obj)
+        xmlutils.put(ical_request, self._calendar, self.path, obj)
 
         self.send_response(client.CREATED)
 
@@ -184,9 +191,11 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     def do_REPORT(self):
         """Manage REPORT request."""
         xml_request = self.rfile.read(int(self.headers["Content-Length"]))
-        answer = xmlutils.report(xml_request, self.calendar, self.path)
+        answer = xmlutils.report(xml_request, self._calendar, self.path)
 
         self.send_response(client.MULTI_STATUS)
         self.send_header("Content-Length", len(answer))
         self.end_headers()
         self.wfile.write(answer)
+
+    # pylint: enable-msg=C0103

+ 177 - 61
radicale/calendar.py

@@ -25,93 +25,209 @@ Define the main classes of a calendar as seen from the server.
 
 """
 
-from radicale import support
+import os
+import codecs
 
+from radicale import config
 
-def hash_tag(vcalendar):
-    """Hash an vcalendar string."""
-    return str(hash(vcalendar))
 
+FOLDER = os.path.expanduser(config.get("storage", "folder"))
+    
 
-class Calendar(object):
-    """Internal calendar class."""
-    def __init__(self, user, cal):
-        """Initialize the calendar with ``cal`` and ``user`` parameters."""
-        # TODO: Use properties from the calendar configuration
-        self.support = support.load()
-        self.encoding = "utf-8"
-        self.owner = "radicale"
-        self.user = user
-        self.cal = cal
-        self.version = "2.0"
-        self.ctag = hash_tag(self.vcalendar)
-
-    def append(self, vcalendar):
-        """Append vcalendar to the calendar."""
-        self.ctag = hash_tag(self.vcalendar)
-        self.support.append(self.cal, vcalendar)
-
-    def remove(self, uid):
-        """Remove object named ``uid`` from the calendar."""
-        self.ctag = hash_tag(self.vcalendar)
-        self.support.remove(self.cal, uid)
-
-    def replace(self, uid, vcalendar):
-        """Replace objet named ``uid`` by ``vcalendar`` in the calendar."""
-        self.ctag = hash_tag(self.vcalendar)
-        self.support.remove(self.cal, uid)
-        self.support.append(self.cal, vcalendar)
+# This function overrides the builtin ``open`` function for this module
+# pylint: disable-msg=W0622
+def open(path, mode="r"):
+    """Open file at ``path`` with ``mode``, automagically managing encoding."""
+    return codecs.open(path, mode, config.get("encoding", "stock"))
+# pylint: enable-msg=W0622
 
-    @property
-    def vcalendar(self):
-        """Unicode calendar from the calendar."""
-        return self.support.read(self.cal)
 
-    @property
-    def etag(self):
-        """Etag from calendar."""
-        return '"%s"' % hash_tag(self.vcalendar)
+class Header(object):
+    """Internal header class."""
+    def __init__(self, text):
+        """Initialize header from ``text``."""
+        self.text = text
 
 
 class Event(object):
     """Internal event class."""
-    def __init__(self, vcalendar):
-        """Initialize event from ``vcalendar``."""
-        self.text = vcalendar
+    tag = "VEVENT"
+
+    def __init__(self, text):
+        """Initialize event from ``text``."""
+        self.text = text
 
     @property
     def etag(self):
         """Etag from event."""
-        return '"%s"' % hash_tag(self.text)
+        return '"%s"' % hash(self.text)
 
 
-class Header(object):
-    """Internal header class."""
-    def __init__(self, vcalendar):
-        """Initialize header from ``vcalendar``."""
-        self.text = vcalendar
+class Todo(object):
+    """Internal todo class."""
+    # This is not a TODO!
+    # pylint: disable-msg=W0511
+    tag = "VTODO"
+    # pylint: enable-msg=W0511
+
+    def __init__(self, text):
+        """Initialize todo from ``text``."""
+        self.text = text
+
+    @property
+    def etag(self):
+        """Etag from todo."""
+        return '"%s"' % hash(self.text)
 
 
 class Timezone(object):
     """Internal timezone class."""
-    def __init__(self, vcalendar):
-        """Initialize timezone from ``vcalendar``."""
-        lines = vcalendar.splitlines()
+    tag = "VTIMEZONE"
+
+    def __init__(self, text):
+        """Initialize timezone from ``text``."""
+        lines = text.splitlines()
         for line in lines:
             if line.startswith("TZID:"):
-                self.id = line.lstrip("TZID:")
+                self.name = line.replace("TZID:", "")
                 break
 
-        self.text = vcalendar
+        self.text = text
 
 
-class Todo(object):
-    """Internal todo class."""
-    def __init__(self, vcalendar):
-        """Initialize todo from ``vcalendar``."""
-        self.text = vcalendar
+class Calendar(object):
+    """Internal calendar class."""
+    def __init__(self, path):
+        """Initialize the calendar with ``cal`` and ``user`` parameters."""
+        # TODO: Use properties from the calendar configuration
+        self.encoding = "utf-8"
+        self.owner = path.split("/")[0]
+        self.path = os.path.join(FOLDER, path.replace("/", os.path.sep))
+        self.ctag = self.etag
+
+    @staticmethod
+    def _parse(text, obj):
+        """Find ``obj.tag`` items in ``text`` text.
+
+        Return a list of items of type ``obj``.
+
+        """
+        items = []
+
+        lines = text.splitlines()
+        in_item = False
+        item_lines = []
+
+        for line in lines:
+            if line.startswith("BEGIN:%s" % obj.tag):
+                in_item = True
+                item_lines = []
+
+            if in_item:
+                item_lines.append(line)
+                if line.startswith("END:%s" % obj.tag):
+                    items.append(obj("\n".join(item_lines)))
+
+        return items
+
+    def append(self, text):
+        """Append ``text`` to calendar."""
+        self.ctag = self.etag
+
+        timezones = self.timezones
+        events = self.events
+        todos = self.todos
+
+        for new_timezone in self._parse(text, Timezone):
+            if new_timezone.name not in [timezone.name
+                                         for timezone in timezones]:
+                timezones.append(new_timezone)
+
+        for new_event in self._parse(text, Event):
+            if new_event.etag not in [event.etag for event in events]:
+                events.append(new_event)
+
+        for new_todo in self._parse(text, Todo):
+            if new_todo.etag not in [todo.etag for todo in todos]:
+                todos.append(new_todo)
+
+        self.write(timezones=timezones, events=events, todos=todos)
+
+    def remove(self, etag):
+        """Remove object named ``etag`` from the calendar."""
+        self.ctag = self.etag
+        todos = [todo for todo in self.todos if todo.etag != etag]
+        events = [event for event in self.events if event.etag != etag]
+
+        self.write(todos=todos, events=events)
+
+    def replace(self, etag, text):
+        """Replace objet named ``etag`` by ``text`` in the calendar."""
+        self.ctag = self.etag
+        self.remove(etag)
+        self.append(text)
+
+    def write(self, headers=None, timezones=None, events=None, todos=None):
+        """Write calendar with given parameters."""
+        headers = headers or self.headers or (
+            Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
+            Header("VERSION:2.0"))
+        timezones = timezones or self.timezones
+        events = events or self.events
+        todos = todos or self.todos
+
+        # Create folder if absent
+        if not os.path.exists(os.path.dirname(self.path)):
+            os.makedirs(os.path.dirname(self.path))
+            
+        text = "\n".join((
+                "BEGIN:VCALENDAR",
+                "\n".join([header.text for header in headers]),
+                "\n".join([timezone.text for timezone in timezones]),
+                "\n".join([todo.text for todo in todos]),
+                "\n".join([event.text for event in events]),
+                "END:VCALENDAR"))
+        return open(self.path, "w").write(text)
 
     @property
     def etag(self):
-        """Etag from todo."""
-        return hash_tag(self.text)
+        """Etag from calendar."""
+        return '"%s"' % hash(self.text)
+
+    @property
+    def text(self):
+        """Calendar as plain text."""
+        try:
+            return open(self.path).read()
+        except IOError:
+            return ""
+
+    @property
+    def headers(self):
+        """Find headers items in calendar."""
+        header_lines = []
+
+        lines = self.text.splitlines()
+        for line in lines:
+            if line.startswith("PRODID:"):
+                header_lines.append(Header(line))
+        for line in lines:
+            if line.startswith("VERSION:"):
+                header_lines.append(Header(line))
+
+        return header_lines
+
+    @property
+    def events(self):
+        """Get list of ``Event`` items in calendar."""
+        return self._parse(self.text, Event)
+
+    @property
+    def todos(self):
+        """Get list of ``Todo`` items in calendar."""
+        return self._parse(self.text, Todo)
+
+    @property
+    def timezones(self):
+        """Get list of ``Timezome`` items in calendar."""
+        return self._parse(self.text, Timezone)

+ 14 - 18
radicale/config.py

@@ -29,10 +29,13 @@ Give a configparser-like interface to read and write configuration.
 
 import os
 import sys
+# Manage Python2/3 different modules
+# pylint: disable-msg=F0401
 try:
     from configparser import RawConfigParser as ConfigParser
 except ImportError:
     from ConfigParser import RawConfigParser as ConfigParser
+# pylint: enable-msg=F0401
 
 
 # Default configuration
@@ -43,34 +46,27 @@ INITIAL_CONFIG = {
         "daemon": "False",
         "ssl": "False",
         "certificate": "/etc/apache2/ssl/server.crt",
-        "key": "/etc/apache2/ssl/server.key",
-        },
+        "key": "/etc/apache2/ssl/server.key"},
     "encoding": {
         "request": "utf-8",
-        "stock": "utf-8",
-        },
+        "stock": "utf-8"},
     "acl": {
         "type": "fake",
         "filename": "/etc/radicale/users",
-        "encryption": "crypt",
-        },
-    "support": {
-        "type": "plain",
-        "folder": os.path.expanduser("~/.config/radicale"),
-        "calendar": "radicale/cal",
-        },
-    }
+        "encryption": "crypt"},
+    "storage": {
+        "folder": os.path.expanduser("~/.config/radicale/calendars")}}
 
 # Create a ConfigParser and configure it
-_CONFIG = ConfigParser()
+_CONFIG_PARSER = ConfigParser()
 
 for section, values in INITIAL_CONFIG.items():
-    _CONFIG.add_section(section)
+    _CONFIG_PARSER.add_section(section)
     for key, value in values.items():
-        _CONFIG.set(section, key, value)
+        _CONFIG_PARSER.set(section, key, value)
 
-_CONFIG.read("/etc/radicale/config")
-_CONFIG.read(os.path.expanduser("~/.config/radicale/config"))
+_CONFIG_PARSER.read("/etc/radicale/config")
+_CONFIG_PARSER.read(os.path.expanduser("~/.config/radicale/config"))
 
 # Wrap config module into ConfigParser instance
-sys.modules[__name__] = _CONFIG
+sys.modules[__name__] = _CONFIG_PARSER

+ 0 - 98
radicale/ical.py

@@ -1,98 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Radicale Server - Calendar Server
-# Copyright © 2008-2010 Guillaume Ayoub
-# Copyright © 2008 Nicolas Kandel
-# Copyright © 2008 Pascal Halter
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
-
-"""
-iCal parsing functions.
-
-"""
-
-# TODO: Manage filters (see xmlutils)
-
-from radicale import calendar
-
-
-def write_calendar(headers=(
-        calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
-        calendar.Header("VERSION:2.0")),
-                   timezones=(), todos=(), events=()):
-    """Create calendar from given parameters."""
-    cal = "\n".join((
-        "BEGIN:VCALENDAR",
-        "\n".join([header.text for header in headers]),
-        "\n".join([timezone.text for timezone in timezones]),
-        "\n".join([todo.text for todo in todos]),
-        "\n".join([event.text for event in events]),
-        "END:VCALENDAR"))
-    return "\n".join([line for line in cal.splitlines() if line])
-
-
-def _parse(vcalendar, tag, obj):
-    """Find ``tag`` items in ``vcalendar``.
-    
-    Return a list of items of type ``obj``.
-
-    """
-    items = []
-
-    lines = vcalendar.splitlines()
-    in_item = False
-    item_lines = []
-
-    for line in lines:
-        if line.startswith("BEGIN:%s" % tag):
-            in_item = True
-            item_lines = []
-
-        if in_item:
-            item_lines.append(line)
-            if line.startswith("END:%s" % tag):
-                items.append(obj("\n".join(item_lines)))
-
-    return items
-
-
-def headers(vcalendar):
-    """Find Headers items in ``vcalendar``."""
-    header_lines = []
-
-    lines = vcalendar.splitlines()
-    for line in lines:
-        if line.startswith("PRODID:"):
-            header_lines.append(calendar.Header(line))
-    for line in lines:
-        if line.startswith("VERSION:"):
-            header_lines.append(calendar.Header(line))
-
-    return header_lines
-
-
-def events(vcalendar):
-    """Get list of ``Event`` from VEVENTS items in ``vcalendar``."""
-    return _parse(vcalendar, "VEVENT", calendar.Event)
-
-
-def todos(vcalendar):
-    """Get list of ``Todo`` from VTODO items in ``vcalendar``."""
-    return _parse(vcalendar, "VTODO", calendar.Todo)
-
-
-def timezones(vcalendar):
-    """Get list of ``Timezome`` from VTIMEZONE items in ``vcalendar``."""
-    return _parse(vcalendar, "VTIMEZONE", calendar.Timezone)

+ 0 - 32
radicale/support/__init__.py

@@ -1,32 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Radicale Server - Calendar Server
-# Copyright © 2008-2010 Guillaume Ayoub
-# Copyright © 2008 Nicolas Kandel
-# Copyright © 2008 Pascal Halter
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
-
-"""
-Calendar storage support configuration.
-
-"""
-
-from radicale import config
-
-def load():
-    """Load list of available storage support managers."""
-    module = __import__("radicale.support", globals(), locals(),
-                        [config.get("support", "type")])
-    return getattr(module, config.get("support", "type"))

+ 0 - 128
radicale/support/plain.py

@@ -1,128 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# This file is part of Radicale Server - Calendar Server
-# Copyright © 2008-2010 Guillaume Ayoub
-# Copyright © 2008 Nicolas Kandel
-# Copyright © 2008 Pascal Halter
-#
-# This library is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
-
-"""
-Plain text storage.
-
-"""
-
-import os
-import posixpath
-import codecs
-
-from radicale import config, ical
-
-FOLDER = os.path.expanduser(config.get("support", "folder"))
-DEFAULT_CALENDAR = config.get("support", "calendar")
-
-
-def _open(path, mode="r"):
-    """Open file at ``path`` with ``mode``, automagically managing encoding."""
-    return codecs.open(path, mode, config.get("encoding", "stock"))
-
-
-def calendars():
-    """List available calendars paths."""
-    available_calendars = []
-
-    for filename in os.listdir(FOLDER):
-        if os.path.isdir(os.path.join(FOLDER, filename)):
-            for cal in os.listdir(os.path.join(FOLDER, filename)):
-                available_calendars.append(posixpath.join(filename, cal))
-
-    return available_calendars
-
-
-def mkcalendar(name):
-    """Write a new calendar called ``name``."""
-    user, cal = name.split(posixpath.sep)
-    if not os.path.exists(os.path.join(FOLDER, user)):
-        os.makedirs(os.path.join(FOLDER, user))
-    descriptor = _open(os.path.join(FOLDER, user, cal), "w")
-    descriptor.write(ical.write_calendar())
-
-
-def read(cal):
-    """Read calendar ``cal``."""
-    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
-    return _open(path).read()
-
-
-def append(cal, vcalendar):
-    """Append ``vcalendar`` to ``cal``."""
-    old_calendar = read(cal)
-    old_timezones = [timezone.id for timezone in ical.timezones(old_calendar)]
-    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
-
-    old_objects = []
-    old_objects.extend([event.etag for event in ical.events(old_calendar)])
-    old_objects.extend([todo.etag for todo in ical.todos(old_calendar)])
-
-    objects = []
-    objects.extend(ical.events(vcalendar))
-    objects.extend(ical.todos(vcalendar))
-
-    for timezone in ical.timezones(vcalendar):
-        if timezone.id not in old_timezones:
-            descriptor = _open(path)
-            lines = [line for line in descriptor.readlines() if line]
-            descriptor.close()
-
-            for i, line in enumerate(timezone.text.splitlines()):
-                lines.insert(2 + i, line + "\n")
-
-            descriptor = _open(path, "w")
-            descriptor.writelines(lines)
-            descriptor.close()
-
-    for obj in objects:
-        if obj.etag not in old_objects:
-            descriptor = _open(path)
-            lines = [line for line in descriptor.readlines() if line]
-            descriptor.close()
-
-            for line in obj.text.splitlines():
-                lines.insert(-1, line + "\n")
-
-            descriptor = _open(path, "w")
-            descriptor.writelines(lines)
-            descriptor.close()
-
-
-def remove(cal, etag):
-    """Remove object named ``etag`` from ``cal``."""
-    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
-
-    cal = read(cal)
-
-    headers = ical.headers(cal)
-    timezones = ical.timezones(cal)
-    todos = [todo for todo in ical.todos(cal) if todo.etag != etag]
-    events = [event for event in ical.events(cal) if event.etag != etag]
-
-    descriptor = _open(path, "w")
-    descriptor.write(ical.write_calendar(headers, timezones, todos, events))
-    descriptor.close()
-
-
-# Create default calendar if not present
-if DEFAULT_CALENDAR:
-    if DEFAULT_CALENDAR not in calendars():
-        mkcalendar(DEFAULT_CALENDAR)

+ 4 - 4
radicale/xmlutils.py

@@ -140,6 +140,7 @@ def propfind(xml_request, calendar, url):
 
 def put(ical_request, calendar, url, obj):
     """Read PUT requests."""
+    # TODO: use url to set hreference
     if obj:
         # PUT is modifying obj
         calendar.replace(obj, ical_request)
@@ -174,11 +175,10 @@ def report(xml_request, calendar, url):
     #       is that really what is needed?
     #       Read rfc4791-9.[6|10] for info
     for hreference in hreferences:
-        headers = ical.headers(calendar.vcalendar)
-        timezones = ical.timezones(calendar.vcalendar)
+        headers = ical.headers(calendar.text)
+        timezones = ical.timezones(calendar.text)
 
-        objects = \
-            ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar)
+        objects = ical.events(calendar.text) + ical.todos(calendar.text)
 
         if not objects:
             # TODO: Read rfc4791-9.[6|10] to find a right answer

+ 1 - 1
setup.py

@@ -69,7 +69,7 @@ setup(
     author_email="guillaume.ayoub@kozea.fr",
     url="http://www.radicale.org/",
     license="GNU GPL v3",
-    packages=["radicale", "radicale.acl", "radicale.support"],
+    packages=["radicale", "radicale.acl"],
     scripts=["radicale.py"],
     cmdclass={'clean': Clean,
               "build_scripts": BuildScripts})