proppatch actually writes properties.

This commit is contained in:
Lukasz Langa 2011-05-24 17:33:57 +02:00
parent 85e283830a
commit 911cd48efe
3 changed files with 89 additions and 40 deletions

View File

@ -260,6 +260,10 @@ class Application(object):
tz = props.get('C:calendar-timezone') tz = props.get('C:calendar-timezone')
if tz: if tz:
calendar.replace('', 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() calendar.write()
return client.CREATED, {}, None return client.CREATED, {}, None
@ -283,11 +287,11 @@ class Application(object):
def proppatch(self, environ, calendar, content): def proppatch(self, environ, calendar, content):
"""Manage PROPPATCH request.""" """Manage PROPPATCH request."""
xmlutils.proppatch(environ["PATH_INFO"], content, calendar) answer = xmlutils.proppatch(environ["PATH_INFO"], content, calendar)
headers = { headers = {
"DAV": "1, calendar-access", "DAV": "1, calendar-access",
"Content-Type": "text/xml"} "Content-Type": "text/xml"}
return client.MULTI_STATUS, headers, None return client.MULTI_STATUS, headers, answer
def put(self, environ, calendar, content): def put(self, environ, calendar, content):
"""Manage PUT request.""" """Manage PUT request."""

View File

@ -25,8 +25,10 @@ Define the main classes of a calendar as seen from the server.
""" """
import os
import codecs import codecs
from contextlib import contextmanager
import json
import os
import time import time
from radicale import config from radicale import config
@ -254,7 +256,9 @@ class Calendar(object):
@property @property
def name(self): def name(self):
"""Calendar name.""" """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 @property
def text(self): def text(self):
@ -322,3 +326,17 @@ class Calendar(object):
modification_time = time.gmtime(os.path.getmtime(self.path)) modification_time = time.gmtime(os.path.getmtime(self.path))
return time.strftime("%a, %d %b %Y %H:%M:%S +0000", modification_time) 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)

View File

@ -93,6 +93,23 @@ def _tag(short_name, local):
return "{%s}%s" % (NAMESPACES[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): 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])
@ -105,28 +122,24 @@ def name_from_path(path, calendar):
return path_parts[-1] if (len(path_parts) - len(calendar_parts)) else None 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.""" """Returns a list of properties as a dictionary."""
result = OrderedDict() 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")) for action in actions:
if not set_element: action_element = root.find(_tag("D", action))
set_element = root if action_element is not None:
break
else:
action_element = root
prop_element = set_element.find(_tag("D", "prop")) prop_element = action_element.find(_tag("D", "prop"))
if prop_element: if prop_element is not None:
for prop in prop_element: for prop in prop_element:
match = CLARK_TAG_REGEX.match(prop.tag) result[_tag_from_clark(prop.tag)] = prop.text
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
return result return result
@ -256,6 +269,27 @@ def propfind(path, xml_request, calendar, depth):
return _pretty_xml(multistatus) 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): def proppatch(path, xml_request, calendar):
"""Read and answer PROPPATCH requests. """Read and answer PROPPATCH requests.
@ -264,13 +298,8 @@ def proppatch(path, xml_request, calendar):
""" """
# Reading request # Reading request
root = ET.fromstring(xml_request.encode("utf8")) root = ET.fromstring(xml_request.encode("utf8"))
props = [] props_to_set = props_from_request(root, actions=('set',))
props_to_remove = props_from_request(root, actions=('remove',))
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)
# Writing answer # Writing answer
multistatus = ET.Element(_tag("D", "multistatus")) multistatus = ET.Element(_tag("D", "multistatus"))
@ -282,19 +311,17 @@ def proppatch(path, xml_request, calendar):
href.text = path href.text = path
response.append(href) response.append(href)
propstat = ET.Element(_tag("D", "propstat")) with calendar.props as calendar_props:
response.append(propstat) for short_name, value in props_to_set.items():
calendar_props[short_name] = value
prop = ET.Element(_tag("D", "prop")) _add_propstat_to(response, short_name, 200)
propstat.append(prop) for short_name in props_to_remove:
try:
for tag in props: del calendar_props[short_name]
element = ET.Element(tag) except KeyError:
prop.append(element) _add_propstat_to(response, short_name, 412)
else:
status = ET.Element(_tag("D", "status")) _add_propstat_to(response, short_name, 200)
status.text = _response(200)
propstat.append(status)
return _pretty_xml(multistatus) return _pretty_xml(multistatus)