Bladeren bron

proppatch actually writes properties.

Lukasz Langa 14 jaren geleden
bovenliggende
commit
911cd48efe
3 gewijzigde bestanden met toevoegingen van 89 en 40 verwijderingen
  1. 6 2
      radicale/__init__.py
  2. 20 2
      radicale/ical.py
  3. 63 36
      radicale/xmlutils.py

+ 6 - 2
radicale/__init__.py

@@ -260,6 +260,10 @@ class Application(object):
         tz = props.get('C:calendar-timezone')
         if tz:
             calendar.replace('', tz)
+            del props['C:calendar-timezone']
+        with calendar.props as calendar_props:
+            for key, value in props.items():
+                calendar_props[key] = value
         calendar.write()
         return client.CREATED, {}, None
 
@@ -283,11 +287,11 @@ class Application(object):
 
     def proppatch(self, environ, calendar, content):
         """Manage PROPPATCH request."""
-        xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
+        answer = xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
         headers = {
             "DAV": "1, calendar-access",
             "Content-Type": "text/xml"}
-        return client.MULTI_STATUS, headers, None
+        return client.MULTI_STATUS, headers, answer
 
     def put(self, environ, calendar, content):
         """Manage PUT request."""

+ 20 - 2
radicale/ical.py

@@ -25,8 +25,10 @@ Define the main classes of a calendar as seen from the server.
 
 """
 
-import os
 import codecs
+from contextlib import contextmanager
+import json
+import os
 import time
 
 from radicale import config
@@ -254,7 +256,9 @@ class Calendar(object):
     @property
     def name(self):
         """Calendar name."""
-        return self.path.split(os.path.sep)[-1]
+        with self.props as props:
+            return props.get('D:displayname',
+                self.path.split(os.path.sep)[-1])
 
     @property
     def text(self):
@@ -322,3 +326,17 @@ class Calendar(object):
 
         modification_time = time.gmtime(os.path.getmtime(self.path))
         return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time)
+
+    @property
+    @contextmanager
+    def props(self):
+        props_path = self.path + '.props'
+        # on enter
+        properties = {}
+        if os.path.exists(props_path):
+            with open(props_path) as prop_file:
+                properties.update(json.load(prop_file))
+        yield properties
+        # on exit
+        with open(props_path, 'w') as prop_file:
+            json.dump(properties, prop_file)

+ 63 - 36
radicale/xmlutils.py

@@ -93,6 +93,23 @@ def _tag(short_name, local):
     return "{%s}%s" % (NAMESPACES[short_name], local)
 
 
+def _tag_from_clark(name):
+    """For a given name using the XML Clark notation returns a human-readable
+    variant of the tag name for known namespaces. Otherwise returns the name
+    as is.
+
+    """
+    match = CLARK_TAG_REGEX.match(name)
+    if match and match.group('namespace') in NAMESPACES_REV:
+        args = {
+            'ns': NAMESPACES_REV[match.group('namespace')],
+            'tag': match.group('tag')}
+        tag_name = '%(ns)s:%(tag)s' % args
+    else:
+        tag_name = prop.tag
+    return tag_name
+
+
 def _response(code):
     """Return full W3C names from HTTP status codes."""
     return "HTTP/1.1 %i %s" % (code, client.responses[code])
@@ -105,28 +122,24 @@ def name_from_path(path, calendar):
     return path_parts[-1] if (len(path_parts) - len(calendar_parts)) else None
 
 
-def props_from_request(xml_request):
+def props_from_request(root, actions=("set", "remove")):
     """Returns a list of properties as a dictionary."""
 
     result = OrderedDict()
-    root = ET.fromstring(xml_request.encode("utf8"))
+    if not isinstance(root, ET.Element):
+        root = ET.fromstring(root.encode("utf8"))
 
-    set_element = root.find(_tag("D", "set"))
-    if not set_element:
-        set_element = root
+    for action in actions:
+        action_element = root.find(_tag("D", action))
+        if action_element is not None:
+            break
+    else:
+        action_element = root
 
-    prop_element = set_element.find(_tag("D", "prop"))
-    if prop_element:
+    prop_element = action_element.find(_tag("D", "prop"))
+    if prop_element is not None:
         for prop in prop_element:
-            match = CLARK_TAG_REGEX.match(prop.tag)
-            if match and match.group('namespace') in NAMESPACES_REV:
-                args = {
-                    'ns': NAMESPACES_REV[match.group('namespace')],
-                    'tag': match.group('tag')}
-                tag_name = '%(ns)s:%(tag)s' % args
-            else:
-                tag_name = prop.tag
-            result[tag_name] = prop.text
+            result[_tag_from_clark(prop.tag)] = prop.text
     return result
 
 
@@ -256,6 +269,27 @@ def propfind(path, xml_request, calendar, depth):
     return _pretty_xml(multistatus)
 
 
+def _add_propstat_to(element, tag, status_number):
+    """Adds a propstat structure to the given element for the
+    following `tag` with the given `status_number`."""
+    propstat = ET.Element(_tag("D", "propstat"))
+    element.append(propstat)
+
+    prop = ET.Element(_tag("D", "prop"))
+    propstat.append(prop)
+
+    if '{' in tag:
+        clark_tag = tag
+    else:
+        clark_tag = _tag(*tag.split(':', 1))
+    prop_tag = ET.Element(clark_tag)
+    prop.append(prop_tag)
+
+    status = ET.Element(_tag("D", "status"))
+    status.text = _response(status_number)
+    propstat.append(status)
+
+
 def proppatch(path, xml_request, calendar):
     """Read and answer PROPPATCH requests.
 
@@ -264,13 +298,8 @@ def proppatch(path, xml_request, calendar):
     """
     # Reading request
     root = ET.fromstring(xml_request.encode("utf8"))
-    props = []
-
-    for action in ("set", "remove"):
-        action_element = root.find(_tag("D", action))
-        if action_element is not None:
-            prop_element = action_element.find(_tag("D", "prop"))
-            props.extend(prop.tag for prop in prop_element)
+    props_to_set = props_from_request(root, actions=('set',))
+    props_to_remove = props_from_request(root, actions=('remove',))
 
     # Writing answer
     multistatus = ET.Element(_tag("D", "multistatus"))
@@ -282,19 +311,17 @@ def proppatch(path, xml_request, calendar):
     href.text = path
     response.append(href)
 
-    propstat = ET.Element(_tag("D", "propstat"))
-    response.append(propstat)
-
-    prop = ET.Element(_tag("D", "prop"))
-    propstat.append(prop)
-
-    for tag in props:
-        element = ET.Element(tag)
-        prop.append(element)
-
-    status = ET.Element(_tag("D", "status"))
-    status.text = _response(200)
-    propstat.append(status)
+    with calendar.props as calendar_props:
+        for short_name, value in props_to_set.items():
+            calendar_props[short_name] = value
+            _add_propstat_to(response, short_name, 200)
+        for short_name in props_to_remove:
+            try:
+                del calendar_props[short_name]
+            except KeyError:
+                _add_propstat_to(response, short_name, 412)
+            else:
+                _add_propstat_to(response, short_name, 200)
 
     return _pretty_xml(multistatus)