Просмотр исходного кода

Code cleaned using Pylint, fixes various minor bugs too.

Guillaume Ayoub 16 лет назад
Родитель
Сommit
21a743fcde

+ 10 - 4
radicale.py

@@ -19,16 +19,21 @@
 # You should have received a copy of the GNU General Public License
 # You should have received a copy of the GNU General Public License
 # along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
 # along with Radicale.  If not, see <http://www.gnu.org/licenses/>.
 
 
+# This file is just a script, allow [a-z0-9]* variable names
+# pylint: disable-msg=C0103
+
+# ``import radicale`` refers to the ``radicale`` module, not ``radicale.py`` 
+# pylint: disable-msg=W0406
+
 """
 """
 Radicale Server entry point.
 Radicale Server entry point.
 
 
 Launch the Radicale Serve according to configuration and command-line
 Launch the Radicale Serve according to configuration and command-line
 arguments.
 arguments.
+
 """
 """
 
 
-# TODO: Manage depth and calendars/collections (see xmlutils)
 # TODO: Manage smart and configurable logs
 # TODO: Manage smart and configurable logs
-# TODO: Manage authentication
 
 
 import os
 import os
 import sys
 import sys
@@ -62,7 +67,7 @@ parser.add_option(
     "-c", "--certificate",
     "-c", "--certificate",
     default=radicale.config.get("server", "certificate"),
     default=radicale.config.get("server", "certificate"),
     help="certificate file ")
     help="certificate file ")
-options, args = parser.parse_args()
+options = parser.parse_args()[0]
 
 
 # Update Radicale configuration according to options
 # Update Radicale configuration according to options
 for option in parser.option_list:
 for option in parser.option_list:
@@ -79,5 +84,6 @@ if options.daemon:
 
 
 # Launch calendar server
 # Launch calendar server
 server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
 server_class = radicale.HTTPSServer if options.ssl else radicale.HTTPServer
-server = server_class((options.host, options.port), radicale.CalendarHTTPHandler)
+server = server_class(
+    (options.host, options.port), radicale.CalendarHTTPHandler)
 server.serve_forever()
 server.serve_forever()

+ 15 - 10
radicale/__init__.py

@@ -33,8 +33,6 @@ should have been included in this package.
 
 
 """
 """
 
 
-# TODO: Manage errors (see xmlutils)
-
 import base64
 import base64
 import socket
 import socket
 try:
 try:
@@ -43,9 +41,10 @@ except ImportError:
     import httplib as client
     import httplib as client
     import BaseHTTPServer as server
     import BaseHTTPServer as server
 
 
-from radicale import acl, config, support, xmlutils
+from radicale import acl, calendar, config, support, xmlutils
+
 
 
-def check(request, function):
+def _check(request, function):
     """Check if user has sufficient rights for performing ``request``."""
     """Check if user has sufficient rights for performing ``request``."""
     authorization = request.headers.get("Authorization", None)
     authorization = request.headers.get("Authorization", None)
     if authorization:
     if authorization:
@@ -64,8 +63,6 @@ def check(request, function):
             "Basic realm=\"Radicale Server - Password Required\"")
             "Basic realm=\"Radicale Server - Password Required\"")
         request.end_headers()
         request.end_headers()
 
 
-# Decorator checking rights before performing request
-check_rights = lambda function: lambda request: check(request, function)
 
 
 class HTTPServer(server.HTTPServer):
 class HTTPServer(server.HTTPServer):
     """HTTP server."""
     """HTTP server."""
@@ -74,6 +71,7 @@ class HTTPServer(server.HTTPServer):
         server.HTTPServer.__init__(self, address, handler)
         server.HTTPServer.__init__(self, address, handler)
         self.acl = acl.load()
         self.acl = acl.load()
 
 
+
 class HTTPSServer(HTTPServer):
 class HTTPSServer(HTTPServer):
     """HTTPS server."""
     """HTTPS server."""
     def __init__(self, address, handler):
     def __init__(self, address, handler):
@@ -91,10 +89,14 @@ class HTTPSServer(HTTPServer):
         self.server_bind()
         self.server_bind()
         self.server_activate()
         self.server_activate()
 
 
+
 class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
 class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
     """HTTP requests handler for calendars."""
     """HTTP requests handler for calendars."""
     _encoding = config.get("encoding", "request")
     _encoding = config.get("encoding", "request")
 
 
+    # Decorator checking rights before performing request
+    check_rights = lambda function: lambda request: _check(request, function)
+
     @property
     @property
     def calendar(self):
     def calendar(self):
         """The ``calendar.Calendar`` object corresponding to the given path."""
         """The ``calendar.Calendar`` object corresponding to the given path."""
@@ -109,9 +111,9 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
         charsets = []
         charsets = []
 
 
         # First append content charset given in the request
         # First append content charset given in the request
-        contentType = self.headers["Content-Type"]
-        if contentType and "charset=" in contentType:
-            charsets.append(contentType.split("charset=")[1].strip())
+        content_type = self.headers["Content-Type"]
+        if content_type and "charset=" in content_type:
+            charsets.append(content_type.split("charset=")[1].strip())
         # Then append default Radicale charset
         # Then append default Radicale charset
         charsets.append(self._encoding)
         charsets.append(self._encoding)
         # Then append various fallbacks
         # Then append various fallbacks
@@ -126,10 +128,13 @@ class CalendarHTTPHandler(server.BaseHTTPRequestHandler):
                 pass
                 pass
         raise UnicodeDecodeError
         raise UnicodeDecodeError
 
 
+    # Naming methods ``do_*`` is OK here
+    # pylint: disable-msg=C0103
+
     @check_rights
     @check_rights
     def do_GET(self):
     def do_GET(self):
         """Manage GET request."""
         """Manage GET request."""
-        answer = self.calendar.vcalendar.encode(_encoding)
+        answer = self.calendar.vcalendar.encode(self._encoding)
 
 
         self.send_response(client.OK)
         self.send_response(client.OK)
         self.send_header("Content-Length", len(answer))
         self.send_header("Content-Length", len(answer))

+ 3 - 0
radicale/acl/__init__.py

@@ -23,11 +23,14 @@ Users and rights management.
 
 
 This module loads a list of users with access rights, according to the acl
 This module loads a list of users with access rights, according to the acl
 configuration.
 configuration.
+
 """
 """
 
 
 from radicale import config
 from radicale import config
 
 
+
 def load():
 def load():
+    """Load list of available ACL managers."""
     module = __import__("radicale.acl", globals(), locals(),
     module = __import__("radicale.acl", globals(), locals(),
                         [config.get("acl", "type")])
                         [config.get("acl", "type")])
     return getattr(module, config.get("acl", "type"))
     return getattr(module, config.get("acl", "type"))

+ 1 - 1
radicale/acl/fake.py

@@ -25,6 +25,6 @@ No rights management.
 
 
 """
 """
 
 
-def has_right(user, password):
+def has_right(*_):
     """Check if ``user``/``password`` couple is valid."""
     """Check if ``user``/``password`` couple is valid."""
     return True
     return True

+ 20 - 12
radicale/acl/htpasswd.py

@@ -33,27 +33,35 @@ import hashlib
 
 
 from radicale import config
 from radicale import config
 
 
-def _plain(hash, password):
-    return hash == password
 
 
-def _crypt(hash, password):
-    return crypt.crypt(password, hash) == hash
+FILENAME = config.get("acl", "filename")
+CHECK_PASSWORD = locals()["_%s" % config.get("acl", "encryption")]
 
 
-def _sha1(hash, password):
-    hash = hash.replace("{SHA}", "").encode("ascii")
+
+def _plain(hash_value, password):
+    """Check if ``hash_value`` and ``password`` match using plain method."""
+    return hash_value == password
+
+
+def _crypt(hash_value, password):
+    """Check if ``hash_value`` and ``password`` match using crypt method."""
+    return crypt.crypt(password, hash_value) == hash_value
+
+
+def _sha1(hash_value, password):
+    """Check if ``hash_value`` and ``password`` match using sha1 method."""
+    hash_value = hash_value.replace("{SHA}", "").encode("ascii")
     password = password.encode(config.get("encoding", "stock"))
     password = password.encode(config.get("encoding", "stock"))
     sha1 = hashlib.sha1()
     sha1 = hashlib.sha1()
     sha1.update(password)
     sha1.update(password)
-    return sha1.digest() == base64.b64decode(hash)
+    return sha1.digest() == base64.b64decode(hash_value)
 
 
-_filename = config.get("acl", "filename")
-_check_password = locals()["_%s" % config.get("acl", "encryption")]
 
 
 def has_right(user, password):
 def has_right(user, password):
     """Check if ``user``/``password`` couple is valid."""
     """Check if ``user``/``password`` couple is valid."""
-    for line in open(_filename).readlines():
+    for line in open(FILENAME).readlines():
         if line.strip():
         if line.strip():
-            login, hash = line.strip().split(":")
+            login, hash_value = line.strip().split(":")
             if login == user:
             if login == user:
-                return _check_password(hash, password)
+                return CHECK_PASSWORD(hash_value, password)
     return False
     return False

+ 11 - 2
radicale/calendar.py

@@ -22,11 +22,16 @@
 Radicale calendar classes.
 Radicale calendar classes.
 
 
 Define the main classes of a calendar as seen from the server.
 Define the main classes of a calendar as seen from the server.
+
 """
 """
 
 
 from radicale import support
 from radicale import support
 
 
-hash_tag = lambda vcalendar: str(hash(vcalendar))
+
+def hash_tag(vcalendar):
+    """Hash an vcalendar string."""
+    return str(hash(vcalendar))
+
 
 
 class Calendar(object):
 class Calendar(object):
     """Internal calendar class."""
     """Internal calendar class."""
@@ -67,6 +72,7 @@ class Calendar(object):
         """Etag from calendar."""
         """Etag from calendar."""
         return '"%s"' % hash_tag(self.vcalendar)
         return '"%s"' % hash_tag(self.vcalendar)
 
 
+
 class Event(object):
 class Event(object):
     """Internal event class."""
     """Internal event class."""
     def __init__(self, vcalendar):
     def __init__(self, vcalendar):
@@ -78,12 +84,14 @@ class Event(object):
         """Etag from event."""
         """Etag from event."""
         return '"%s"' % hash_tag(self.text)
         return '"%s"' % hash_tag(self.text)
 
 
+
 class Header(object):
 class Header(object):
     """Internal header class."""
     """Internal header class."""
     def __init__(self, vcalendar):
     def __init__(self, vcalendar):
         """Initialize header from ``vcalendar``."""
         """Initialize header from ``vcalendar``."""
         self.text = vcalendar
         self.text = vcalendar
 
 
+
 class Timezone(object):
 class Timezone(object):
     """Internal timezone class."""
     """Internal timezone class."""
     def __init__(self, vcalendar):
     def __init__(self, vcalendar):
@@ -91,11 +99,12 @@ class Timezone(object):
         lines = vcalendar.splitlines()
         lines = vcalendar.splitlines()
         for line in lines:
         for line in lines:
             if line.startswith("TZID:"):
             if line.startswith("TZID:"):
-                self.tzid = line.lstrip("TZID:")
+                self.id = line.lstrip("TZID:")
                 break
                 break
 
 
         self.text = vcalendar
         self.text = vcalendar
 
 
+
 class Todo(object):
 class Todo(object):
     """Internal todo class."""
     """Internal todo class."""
     def __init__(self, vcalendar):
     def __init__(self, vcalendar):

+ 16 - 21
radicale/config.py

@@ -22,26 +22,21 @@
 Radicale configuration module.
 Radicale configuration module.
 
 
 Give a configparser-like interface to read and write configuration.
 Give a configparser-like interface to read and write configuration.
+
 """
 """
 
 
 # TODO: Use abstract filenames for other platforms
 # TODO: Use abstract filenames for other platforms
 
 
 import os
 import os
+import sys
 try:
 try:
     from configparser import RawConfigParser as ConfigParser
     from configparser import RawConfigParser as ConfigParser
 except ImportError:
 except ImportError:
     from ConfigParser import RawConfigParser as ConfigParser
     from ConfigParser import RawConfigParser as ConfigParser
 
 
-_config = ConfigParser()
-get = _config.get
-set = _config.set
-getboolean = _config.getboolean
-getint = _config.getint
-getfloat = _config.getfloat
-options = _config.options
-items = _config.items
 
 
-_initial = {
+# Default configuration
+INITIAL_CONFIG = {
     "server": {
     "server": {
         "host": "",
         "host": "",
         "port": "5232",
         "port": "5232",
@@ -49,17 +44,11 @@ _initial = {
         "ssl": "False",
         "ssl": "False",
         "certificate": "/etc/apache2/ssl/server.crt",
         "certificate": "/etc/apache2/ssl/server.crt",
         "key": "/etc/apache2/ssl/server.key",
         "key": "/etc/apache2/ssl/server.key",
-        #"log": "/var/www/radicale/server.log",
         },
         },
     "encoding": {
     "encoding": {
         "request": "utf-8",
         "request": "utf-8",
         "stock": "utf-8",
         "stock": "utf-8",
         },
         },
-    "namespace": {
-        "C": "urn:ietf:params:xml:ns:caldav",
-        "D": "DAV:",
-        "CS": "http://calendarserver.org/ns/",
-        },
     "acl": {
     "acl": {
         "type": "fake",
         "type": "fake",
         "filename": "/etc/radicale/users",
         "filename": "/etc/radicale/users",
@@ -68,14 +57,20 @@ _initial = {
     "support": {
     "support": {
         "type": "plain",
         "type": "plain",
         "folder": os.path.expanduser("~/.config/radicale"),
         "folder": os.path.expanduser("~/.config/radicale"),
-        "calendar": "radicale/calendar",
+        "calendar": "radicale/cal",
         },
         },
     }
     }
 
 
-for section, values in _initial.items():
-    _config.add_section(section)
+# Create a ConfigParser and configure it
+_CONFIG = ConfigParser()
+
+for section, values in INITIAL_CONFIG.items():
+    _CONFIG.add_section(section)
     for key, value in values.items():
     for key, value in values.items():
-        _config.set(section, key, value)
+        _CONFIG.set(section, key, value)
+
+_CONFIG.read("/etc/radicale/config")
+_CONFIG.read(os.path.expanduser("~/.config/radicale/config"))
 
 
-_config.read("/etc/radicale/config")
-_config.read(os.path.expanduser("~/.config/radicale/config"))
+# Wrap config module into ConfigParser instance
+sys.modules[__name__] = _CONFIG

+ 43 - 29
radicale/ical.py

@@ -20,18 +20,19 @@
 
 
 """
 """
 iCal parsing functions.
 iCal parsing functions.
+
 """
 """
 
 
 # TODO: Manage filters (see xmlutils)
 # TODO: Manage filters (see xmlutils)
 
 
 from radicale import calendar
 from radicale import calendar
 
 
-def write_calendar(headers=[
+
+def write_calendar(headers=(
         calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
         calendar.Header("PRODID:-//Radicale//NONSGML Radicale Server//EN"),
-        calendar.Header("VERSION:2.0")],
-                  timezones=[], todos=[], events=[]):
-    """Create calendar from ``headers``, ``timezones``, ``todos``, ``events``."""
-    # TODO: Manage encoding and EOL
+        calendar.Header("VERSION:2.0")),
+                   timezones=(), todos=(), events=()):
+    """Create calendar from given parameters."""
     cal = "\n".join((
     cal = "\n".join((
         "BEGIN:VCALENDAR",
         "BEGIN:VCALENDAR",
         "\n".join([header.text for header in headers]),
         "\n".join([header.text for header in headers]),
@@ -41,44 +42,57 @@ def write_calendar(headers=[
         "END:VCALENDAR"))
         "END:VCALENDAR"))
     return "\n".join([line for line in cal.splitlines() if line])
     return "\n".join([line for line in cal.splitlines() if line])
 
 
-def headers(vcalendar):
-    """Find Headers items in ``vcalendar``."""
-    headers = []
-
-    lines = vcalendar.splitlines()
-    for line in lines:
-        if line.startswith("PRODID:"):
-            headers.append(calendar.Header(line))
-    for line in lines:
-        if line.startswith("VERSION:"):
-            headers.append(calendar.Header(line))
-
-    return headers
 
 
 def _parse(vcalendar, tag, obj):
 def _parse(vcalendar, tag, obj):
     """Find ``tag`` items in ``vcalendar``.
     """Find ``tag`` items in ``vcalendar``.
     
     
     Return a list of items of type ``obj``.
     Return a list of items of type ``obj``.
+
     """
     """
     items = []
     items = []
 
 
     lines = vcalendar.splitlines()
     lines = vcalendar.splitlines()
-    inItem = False
-    itemLines = []
+    in_item = False
+    item_lines = []
 
 
     for line in lines:
     for line in lines:
         if line.startswith("BEGIN:%s" % tag):
         if line.startswith("BEGIN:%s" % tag):
-            inItem = True
-            itemLines = []
+            in_item = True
+            item_lines = []
 
 
-        if inItem:
-            # TODO: Manage encoding
-            itemLines.append(line)
+        if in_item:
+            item_lines.append(line)
             if line.startswith("END:%s" % tag):
             if line.startswith("END:%s" % tag):
-                items.append(obj("\n".join(itemLines)))
+                items.append(obj("\n".join(item_lines)))
 
 
     return items
     return items
 
 
-events = lambda vcalendar: _parse(vcalendar, "VEVENT", calendar.Event)
-todos = lambda vcalendar: _parse(vcalendar, "VTODO", calendar.Todo)
-timezones = lambda vcalendar: _parse(vcalendar, "VTIMEZONE", calendar.Timezone)
+
+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)

+ 2 - 0
radicale/support/__init__.py

@@ -20,11 +20,13 @@
 
 
 """
 """
 Calendar storage support configuration.
 Calendar storage support configuration.
+
 """
 """
 
 
 from radicale import config
 from radicale import config
 
 
 def load():
 def load():
+    """Load list of available storage support managers."""
     module = __import__("radicale.support", globals(), locals(),
     module = __import__("radicale.support", globals(), locals(),
                         [config.get("support", "type")])
                         [config.get("support", "type")])
     return getattr(module, config.get("support", "type"))
     return getattr(module, config.get("support", "type"))

+ 47 - 38
radicale/support/plain.py

@@ -20,6 +20,7 @@
 
 
 """
 """
 Plain text storage.
 Plain text storage.
+
 """
 """
 
 
 import os
 import os
@@ -28,39 +29,47 @@ import codecs
 
 
 from radicale import config, ical
 from radicale import config, ical
 
 
-_folder = os.path.expanduser(config.get("support", "folder"))
+FOLDER = os.path.expanduser(config.get("support", "folder"))
+DEFAULT_CALENDAR = config.get("support", "calendar")
+
 
 
 def _open(path, mode="r"):
 def _open(path, mode="r"):
+    """Open file at ``path`` with ``mode``, automagically managing encoding."""
     return codecs.open(path, mode, config.get("encoding", "stock"))
     return codecs.open(path, mode, config.get("encoding", "stock"))
 
 
+
 def calendars():
 def calendars():
     """List available calendars paths."""
     """List available calendars paths."""
-    calendars = []
+    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))
 
 
-    for folder in os.listdir(_folder):
-        for cal in os.listdir(os.path.join(_folder, folder)):
-            calendars.append(posixpath.join(folder, cal))
+    return available_calendars
 
 
-    return calendars
 
 
 def mkcalendar(name):
 def mkcalendar(name):
     """Write a new calendar called ``name``."""
     """Write a new calendar called ``name``."""
     user, cal = name.split(posixpath.sep)
     user, cal = name.split(posixpath.sep)
-    if not os.path.exists(os.path.join(_folder, user)):
-        os.makedirs(os.path.join(_folder, user))
-    fd = _open(os.path.join(_folder, user, cal), "w")
-    fd.write(ical.write_calendar())
+    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):
 def read(cal):
     """Read calendar ``cal``."""
     """Read calendar ``cal``."""
-    path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
+    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
     return _open(path).read()
     return _open(path).read()
 
 
+
 def append(cal, vcalendar):
 def append(cal, vcalendar):
     """Append ``vcalendar`` to ``cal``."""
     """Append ``vcalendar`` to ``cal``."""
     old_calendar = read(cal)
     old_calendar = read(cal)
-    old_tzs = [tz.tzid for tz in ical.timezones(old_calendar)]
-    path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
+    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 = []
     old_objects.extend([event.etag for event in ical.events(old_calendar)])
     old_objects.extend([event.etag for event in ical.events(old_calendar)])
@@ -70,37 +79,36 @@ def append(cal, vcalendar):
     objects.extend(ical.events(vcalendar))
     objects.extend(ical.events(vcalendar))
     objects.extend(ical.todos(vcalendar))
     objects.extend(ical.todos(vcalendar))
 
 
-    for tz in ical.timezones(vcalendar):
-        if tz.tzid not in old_tzs:
-            # TODO: Manage position and EOL
-            fd = _open(path)
-            lines = [line for line in fd.readlines() if line]
-            fd.close()
+    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(tz.text.splitlines()):
+            for i, line in enumerate(timezone.text.splitlines()):
                 lines.insert(2 + i, line + "\n")
                 lines.insert(2 + i, line + "\n")
 
 
-            fd = _open(path, "w")
-            fd.writelines(lines)
-            fd.close()
+            descriptor = _open(path, "w")
+            descriptor.writelines(lines)
+            descriptor.close()
 
 
     for obj in objects:
     for obj in objects:
         if obj.etag not in old_objects:
         if obj.etag not in old_objects:
-            # TODO: Manage position and EOL
-            fd = _open(path)
-            lines = [line for line in fd.readlines() if line]
-            fd.close()
+            descriptor = _open(path)
+            lines = [line for line in descriptor.readlines() if line]
+            descriptor.close()
 
 
             for line in obj.text.splitlines():
             for line in obj.text.splitlines():
                 lines.insert(-1, line + "\n")
                 lines.insert(-1, line + "\n")
 
 
-            fd = _open(path, "w")
-            fd.writelines(lines)
-            fd.close()
+            descriptor = _open(path, "w")
+            descriptor.writelines(lines)
+            descriptor.close()
+
 
 
 def remove(cal, etag):
 def remove(cal, etag):
     """Remove object named ``etag`` from ``cal``."""
     """Remove object named ``etag`` from ``cal``."""
-    path = os.path.join(_folder, cal.replace(posixpath.sep, os.path.sep))
+    path = os.path.join(FOLDER, cal.replace(posixpath.sep, os.path.sep))
 
 
     cal = read(cal)
     cal = read(cal)
 
 
@@ -109,11 +117,12 @@ def remove(cal, etag):
     todos = [todo for todo in ical.todos(cal) if todo.etag != etag]
     todos = [todo for todo in ical.todos(cal) if todo.etag != etag]
     events = [event for event in ical.events(cal) if event.etag != etag]
     events = [event for event in ical.events(cal) if event.etag != etag]
 
 
-    fd = _open(path, "w")
-    fd.write(ical.write_calendar(headers, timezones, todos, events))
-    fd.close()
+    descriptor = _open(path, "w")
+    descriptor.write(ical.write_calendar(headers, timezones, todos, events))
+    descriptor.close()
+
 
 
-if config.get("support", "calendar"):
-    user, cal = config.get("support", "calendar").split(posixpath.sep)
-    if not os.path.exists(os.path.join(_folder, user, cal)):
-        mkcalendar(config.get("support", "calendar"))
+# Create default calendar if not present
+if DEFAULT_CALENDAR:
+    if DEFAULT_CALENDAR not in calendars():
+        mkcalendar(DEFAULT_CALENDAR)

+ 56 - 57
radicale/xmlutils.py

@@ -24,33 +24,42 @@ XML and iCal requests manager.
 Note that all these functions need to receive unicode objects for full
 Note that all these functions need to receive unicode objects for full
 iCal requests (PUT) and string objects with charset correctly defined
 iCal requests (PUT) and string objects with charset correctly defined
 in them for XML requests (all but PUT).
 in them for XML requests (all but PUT).
+
 """
 """
 
 
-# TODO: Manage errors (see __init__)
-# TODO: Manage depth and calendars/collections (see main)
+# TODO: Manage depth and calendars/collections
 
 
 import xml.etree.ElementTree as ET
 import xml.etree.ElementTree as ET
 
 
 from radicale import client, config, ical
 from radicale import client, config, ical
 
 
+
 # TODO: This is a well-known and accepted hack for ET to avoid ET from renaming
 # TODO: This is a well-known and accepted hack for ET to avoid ET from renaming
 #       namespaces, which is accepted in XML norm but often not in XML
 #       namespaces, which is accepted in XML norm but often not in XML
 #       readers. Is there another clean solution to force namespaces?
 #       readers. Is there another clean solution to force namespaces?
-for key, value in config.items("namespace"):
+PROTECTED_NAMESPACES = {
+    "C": "urn:ietf:params:xml:ns:caldav",
+    "D": "DAV:",
+    "CS": "http://calendarserver.org/ns/"}
+for key, value in PROTECTED_NAMESPACES.items():
     ET._namespace_map[value] = key
     ET._namespace_map[value] = key
 
 
+
 def _tag(short_name, local):
 def _tag(short_name, local):
     """Get XML Clark notation {uri(``short_name``)}``local``."""
     """Get XML Clark notation {uri(``short_name``)}``local``."""
-    return "{%s}%s" % (config.get("namespace", short_name), local)
+    return "{%s}%s" % (PROTECTED_NAMESPACES[short_name], local)
+
 
 
 def _response(code):
 def _response(code):
     """Return full W3C names from HTTP status codes."""
     """Return full W3C names from HTTP status codes."""
     return "HTTP/1.1 %i %s" % (code, client.responses[code])
     return "HTTP/1.1 %i %s" % (code, client.responses[code])
 
 
+
 def delete(obj, calendar, url):
 def delete(obj, calendar, url):
     """Read and answer DELETE requests.
     """Read and answer DELETE requests.
 
 
     Read rfc4918-9.6 for info.
     Read rfc4918-9.6 for info.
+
     """
     """
     # Reading request
     # Reading request
     calendar.remove(obj)
     calendar.remove(obj)
@@ -74,13 +83,14 @@ def propfind(xml_request, calendar, url):
     """Read and answer PROPFIND requests.
     """Read and answer PROPFIND requests.
 
 
     Read rfc4918-9.1 for info.
     Read rfc4918-9.1 for info.
+
     """
     """
     # Reading request
     # Reading request
     root = ET.fromstring(xml_request)
     root = ET.fromstring(xml_request)
 
 
-    propElement = root.find(_tag("D", "prop"))
-    propList = propElement.getchildren()
-    properties = [property.tag for property in propList]
+    prop_element = root.find(_tag("D", "prop"))
+    prop_list = prop_element.getchildren()
+    props = [prop.tag for prop in prop_list]
     
     
     # Writing answer
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
     multistatus = ET.Element(_tag("D", "multistatus"))
@@ -97,30 +107,30 @@ def propfind(xml_request, calendar, url):
     prop = ET.Element(_tag("D", "prop"))
     prop = ET.Element(_tag("D", "prop"))
     propstat.append(prop)
     propstat.append(prop)
 
 
-    if _tag("D", "resourcetype") in properties:
-        resourcetype = ET.Element(_tag("D", "resourcetype"))
-        resourcetype.append(ET.Element(_tag("C", "calendar")))
-        prop.append(resourcetype)
+    if _tag("D", "resourcetype") in props:
+        element = ET.Element(_tag("D", "resourcetype"))
+        element.append(ET.Element(_tag("C", "calendar")))
+        prop.append(element)
 
 
-    if _tag("D", "owner") in properties:
-        owner = ET.Element(_tag("D", "owner"))
-        owner.text = calendar.owner
-        prop.append(owner)
+    if _tag("D", "owner") in props:
+        element = ET.Element(_tag("D", "owner"))
+        element.text = calendar.owner
+        prop.append(element)
 
 
-    if _tag("D", "getcontenttype") in properties:
-        getcontenttype = ET.Element(_tag("D", "getcontenttype"))
-        getcontenttype.text = "text/calendar"
-        prop.append(getcontenttype)
+    if _tag("D", "getcontenttype") in props:
+        element = ET.Element(_tag("D", "getcontenttype"))
+        element.text = "text/calendar"
+        prop.append(element)
 
 
-    if _tag("D", "getetag") in properties:
-        getetag = ET.Element(_tag("D", "getetag"))
-        getetag.text = calendar.etag
-        prop.append(getetag)
+    if _tag("D", "getetag") in props:
+        element = ET.Element(_tag("D", "getetag"))
+        element.text = calendar.etag
+        prop.append(element)
 
 
-    if _tag("CS", "getctag") in properties:
-        getctag = ET.Element(_tag("CS", "getctag"))
-        getctag.text = calendar.ctag
-        prop.append(getctag)
+    if _tag("CS", "getctag") in props:
+        element = ET.Element(_tag("CS", "getctag"))
+        element.text = calendar.ctag
+        prop.append(element)
 
 
     status = ET.Element(_tag("D", "status"))
     status = ET.Element(_tag("D", "status"))
     status.text = _response(200)
     status.text = _response(200)
@@ -128,40 +138,32 @@ def propfind(xml_request, calendar, url):
 
 
     return ET.tostring(multistatus, config.get("encoding", "request"))
     return ET.tostring(multistatus, config.get("encoding", "request"))
 
 
-def put(icalRequest, calendar, url, obj):
+def put(ical_request, calendar, url, obj):
     """Read PUT requests."""
     """Read PUT requests."""
     if obj:
     if obj:
         # PUT is modifying obj
         # PUT is modifying obj
-        calendar.replace(obj, icalRequest)
+        calendar.replace(obj, ical_request)
     else:
     else:
         # PUT is adding a new object
         # PUT is adding a new object
-        calendar.append(icalRequest)
+        calendar.append(ical_request)
 
 
 def report(xml_request, calendar, url):
 def report(xml_request, calendar, url):
     """Read and answer REPORT requests.
     """Read and answer REPORT requests.
 
 
     Read rfc3253-3.6 for info.
     Read rfc3253-3.6 for info.
+
     """
     """
     # Reading request
     # Reading request
     root = ET.fromstring(xml_request)
     root = ET.fromstring(xml_request)
 
 
-    propElement = root.find(_tag("D", "prop"))
-    propList = propElement.getchildren()
-    properties = [property.tag for property in propList]
-
-    filters = {}
-    filterElement = root.find(_tag("C", "filter"))
-    filterList = propElement.getchildren()
-    # TODO: This should be recursive
-    # TODO: Really manage filters (see ical)
-    for filter in filterList:
-        sub = filters[filter.get("name")] = {}
-        for subfilter in filter.getchildren():
-            sub[subfilter.get("name")] = {}
+    prop_element = root.find(_tag("D", "prop"))
+    prop_list = prop_element.getchildren()
+    props = [prop.tag for prop in prop_list]
 
 
     if root.tag == _tag("C", "calendar-multiget"):
     if root.tag == _tag("C", "calendar-multiget"):
         # Read rfc4791-7.9 for info
         # Read rfc4791-7.9 for info
-        hreferences = set([hrefElement.text for hrefElement in root.findall(_tag("D", "href"))])
+        hreferences = set([href_element.text for href_element
+                           in root.findall(_tag("D", "href"))])
     else:
     else:
         hreferences = [url]
         hreferences = [url]
 
 
@@ -173,12 +175,10 @@ def report(xml_request, calendar, url):
     #       Read rfc4791-9.[6|10] for info
     #       Read rfc4791-9.[6|10] for info
     for hreference in hreferences:
     for hreference in hreferences:
         headers = ical.headers(calendar.vcalendar)
         headers = ical.headers(calendar.vcalendar)
-        # TODO: Define timezones by obj
         timezones = ical.timezones(calendar.vcalendar)
         timezones = ical.timezones(calendar.vcalendar)
 
 
-        objects = []
-        objects.extend(ical.events(calendar.vcalendar))
-        objects.extend(ical.todos(calendar.vcalendar))
+        objects = \
+            ical.events(calendar.vcalendar) + ical.todos(calendar.vcalendar)
 
 
         if not objects:
         if not objects:
             # TODO: Read rfc4791-9.[6|10] to find a right answer
             # TODO: Read rfc4791-9.[6|10] to find a right answer
@@ -209,17 +209,16 @@ def report(xml_request, calendar, url):
             prop = ET.Element(_tag("D", "prop"))
             prop = ET.Element(_tag("D", "prop"))
             propstat.append(prop)
             propstat.append(prop)
 
 
-            if _tag("D", "getetag") in properties:
-                # TODO: Can UID and ETAG be the same?
-                getetag = ET.Element(_tag("D", "getetag"))
-                getetag.text = obj.etag
-                prop.append(getetag)
+            if _tag("D", "getetag") in props:
+                element = ET.Element(_tag("D", "getetag"))
+                element.text = obj.etag
+                prop.append(element)
 
 
-            if _tag("C", "calendar-data") in properties:
-                cdata = ET.Element(_tag("C", "calendar-data"))
+            if _tag("C", "calendar-data") in props:
+                element = ET.Element(_tag("C", "calendar-data"))
                 # TODO: Maybe assume that events and todos are not the same
                 # TODO: Maybe assume that events and todos are not the same
-                cdata.text = ical.write_calendar(headers, timezones, [obj])
-                prop.append(cdata)
+                element.text = ical.write_calendar(headers, timezones, [obj])
+                prop.append(element)
 
 
             status = ET.Element(_tag("D", "status"))
             status = ET.Element(_tag("D", "status"))
             status.text = _response(200)
             status.text = _response(200)