[CalendarServer-changes] [3872] CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/ Twisted
source_changes at macosforge.org
source_changes at macosforge.org
Mon Mar 16 13:13:22 PDT 2009
Revision: 3872
http://trac.macosforge.org/projects/calendarserver/changeset/3872
Author: exarkun at twistedmatrix.com
Date: 2009-03-16 13:13:22 -0700 (Mon, 16 Mar 2009)
Log Message:
-----------
Update xattrprops to apply to Twisted; add some unit tests
Modified Paths:
--------------
CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
Added Paths:
-----------
CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.test.test_xattrprops.patch
Added: CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.test.test_xattrprops.patch
===================================================================
--- CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.test.test_xattrprops.patch (rev 0)
+++ CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.test.test_xattrprops.patch 2009-03-16 20:13:22 UTC (rev 3872)
@@ -0,0 +1,238 @@
+Index: twisted/web2/dav/test/test_xattrprops.py
+===================================================================
+--- twisted/web2/dav/test/test_xattrprops.py (revision 0)
++++ twisted/web2/dav/test/test_xattrprops.py (revision 0)
+@@ -0,0 +1,233 @@
++# Copyright (c) 2009 Twisted Matrix Laboratories.
++# See LICENSE for details.
++
++"""
++Tests for L{twisted.web2.dav.xattrprops}.
++"""
++
++from zlib import compress, decompress
++from pickle import dumps
++from cPickle import UnpicklingError
++
++from twisted.python.filepath import FilePath
++from twisted.trial.unittest import TestCase
++from twisted.web2.responsecode import NOT_FOUND, INTERNAL_SERVER_ERROR
++from twisted.web2.responsecode import FORBIDDEN
++from twisted.web2.http import HTTPError
++from twisted.web2.dav.static import DAVFile
++from twisted.web2.dav.davxml import Depth, WebDAVDocument
++
++try:
++ from twisted.web2.dav.xattrprops import xattrPropertyStore
++except ImportError:
++ xattrPropertyStore = None
++else:
++ from xattr import xattr
++
++class ExtendedAttributesPropertyStoreTests(TestCase):
++ """
++ Tests for L{xattrPropertyStore}.
++ """
++ if xattrPropertyStore is None:
++ skip = "xattr package missing, cannot test xattr property store"
++
++ def setUp(self):
++ """
++ Create a resource and a xattr property store for it.
++ """
++ self.resourcePath = FilePath(self.mktemp())
++ self.resourcePath.setContent("")
++ self.attrs = xattr(self.resourcePath.path)
++ self.resource = DAVFile(self.resourcePath.path)
++ self.propertyStore = xattrPropertyStore(self.resource)
++
++
++ def test_getAbsent(self):
++ """
++ L{xattrPropertyStore.get} raises L{HTTPError} with a I{NOT FOUND}
++ response code if passed the name of an attribute for which there is no
++ corresponding value.
++ """
++ error = self.assertRaises(HTTPError, self.propertyStore.get, ("foo", "bar"))
++ self.assertEquals(error.response.code, NOT_FOUND)
++
++
++ def _makeValue(self):
++ """
++ Create and return any old WebDAVDocument for use by the get tests.
++ """
++ element = Depth("0")
++ document = WebDAVDocument(element)
++ return document
++
++
++ def _setValue(self, originalDocument, value):
++ element = originalDocument.root_element
++ attribute = (
++ self.propertyStore.deadPropertyXattrPrefix +
++ "{%s}%s" % element.qname())
++ self.attrs[attribute] = value
++
++
++ def _getValue(self, originalDocument):
++ element = originalDocument.root_element
++ attribute = (
++ self.propertyStore.deadPropertyXattrPrefix +
++ "{%s}%s" % element.qname())
++ return self.attrs[attribute]
++
++
++ def _checkValue(self, originalDocument):
++ property = originalDocument.root_element.qname()
++
++ # Try to load it via xattrPropertyStore.get
++ loadedDocument = self.propertyStore.get(property)
++
++ # XXX Why isn't this a WebDAVDocument?
++ self.assertIsInstance(loadedDocument, Depth)
++ self.assertEquals(str(loadedDocument), "0")
++
++
++ def test_getXML(self):
++ """
++ If there is an XML document associated with the property name passed to
++ L{xattrPropertyStore.get}, that value is parsed into a
++ L{WebDAVDocument}, the root element of which C{get} then returns.
++ """
++ document = self._makeValue()
++ self._setValue(document, document.toxml())
++ self._checkValue(document)
++
++
++ def test_getCompressed(self):
++ """
++ If there is a compressed value associated with the property name passed
++ to L{xattrPropertyStore.get}, that value is decompressed and parsed
++ into a L{WebDAVDocument}, the root element of which C{get} then
++ returns.
++ """
++ document = self._makeValue()
++ self._setValue(document, compress(document.toxml()))
++ self._checkValue(document)
++
++
++ def test_getPickled(self):
++ """
++ If there is a pickled document associated with the property name passed
++ to L{xattrPropertyStore.get}, that value is unpickled into a
++ L{WebDAVDocument}, the root element of which is returned.
++ """
++ document = self._makeValue()
++ self._setValue(document, dumps(document))
++ self._checkValue(document)
++
++
++ def test_getUpgradeXML(self):
++ """
++ If the value associated with the property name passed to
++ L{xattrPropertyStore.get} is an uncompressed XML document, it is
++ upgraded on access by compressing it.
++ """
++ document = self._makeValue()
++ originalValue = document.toxml()
++ self._setValue(document, originalValue)
++ self._checkValue(document)
++ self.assertEquals(
++ decompress(self._getValue(document)), originalValue)
++
++
++ def test_getUpgradeCompressedPickle(self):
++ """
++ If the value associated with the property name passed to
++ L{xattrPropertyStore.get} is a compressed pickled document, it is
++ upgraded on access to the compressed XML format.
++ """
++ document = self._makeValue()
++ self._setValue(document, compress(dumps(document)))
++ self._checkValue(document)
++ self.assertEquals(
++ decompress(self._getValue(document)), document.toxml())
++
++
++ def test_getInvalid(self):
++ """
++ If the value associated with the property name passed to
++ L{xattrPropertyStore.get} cannot be interpreted, an error is logged and
++ L{HTTPError} is raised with the I{INTERNAL SERVER ERROR} response code.
++ """
++ document = self._makeValue()
++ self._setValue(
++ document,
++ "random garbage goes here! \0 that nul is definitely garbage")
++
++ property = document.root_element.qname()
++ error = self.assertRaises(HTTPError, self.propertyStore.get, property)
++ self.assertEquals(error.response.code, INTERNAL_SERVER_ERROR)
++ self.assertEquals(
++ len(self.flushLoggedErrors(UnpicklingError)), 1)
++
++
++ def test_set(self):
++ """
++ L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
++ compressed XML document representing it in an extended attribute.
++ """
++ document = self._makeValue()
++ self.propertyStore.set(document.root_element)
++ self.assertEquals(
++ decompress(self._getValue(document)), document.toxml())
++
++
++ def test_delete(self):
++ """
++ L{xattrPropertyStore.delete} deletes the named property.
++ """
++ document = self._makeValue()
++ self.propertyStore.set(document.root_element)
++ self.propertyStore.delete(document.root_element.qname())
++ self.assertRaises(KeyError, self._getValue, document)
++
++
++ def test_deleteNonExistent(self):
++ """
++ L{xattrPropertyStore.delete} does nothing if passed a property which
++ has no value.
++ """
++ document = self._makeValue()
++ self.propertyStore.delete(document.root_element.qname())
++ self.assertRaises(KeyError, self._getValue, document)
++
++
++ def test_deleteErrors(self):
++ """
++ If there is a problem deleting the specified property (aside from the
++ property not existing), L{xattrPropertyStore.delete} raises
++ L{HTTPError} with a status code which is determined by the nature of
++ the problem.
++ """
++ # Remove access from the directory containing the file so that deleting
++ # extended attributes of it fails with EPERM.
++ self.resourcePath.remove()
++
++ # Try to delete a property from it - and fail.
++ document = self._makeValue()
++ error = self.assertRaises(
++ HTTPError,
++ self.propertyStore.delete, document.root_element.qname())
++
++ # Make sure that the status is FORBIDDEN, a roughly reasonable mapping
++ # of the EPERM failure.
++ self.assertEquals(error.response.code, FORBIDDEN)
++
++
++ def test_contains(self):
++ """
++ L{xattrPropertyStore.contains} returns C{True} if the given property
++ has a value, C{False} otherwise.
++ """
++ document = self._makeValue()
++ self.assertFalse(
++ self.propertyStore.contains(document.root_element.qname()))
++ self._setValue(document, document.toxml())
++ self.assertTrue(
++ self.propertyStore.contains(document.root_element.qname()))
Modified: CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
===================================================================
--- CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch 2009-03-16 19:45:17 UTC (rev 3871)
+++ CalendarServer/branches/exarkun/update-twisted-3816/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch 2009-03-16 20:13:22 UTC (rev 3872)
@@ -1,72 +1,78 @@
Index: twisted/web2/dav/xattrprops.py
===================================================================
---- twisted/web2/dav/xattrprops.py (revision 19773)
+--- twisted/web2/dav/xattrprops.py (revision 26389)
+++ twisted/web2/dav/xattrprops.py (working copy)
-@@ -33,15 +33,24 @@
-
+@@ -34,21 +34,27 @@
import urllib
import sys
-+import zlib
-+from time import sleep
-+from random import random
-+from errno import EAGAIN
-+from zlib import compress, decompress
-+from cPickle import loads as unpickle, PicklingError, UnpicklingError
+ import zlib
++
++from operator import setitem
+ from time import sleep
+ from random import random
+ from errno import EAGAIN
+ from zlib import compress, decompress
++from cPickle import UnpicklingError, loads as unpickle
import xattr
if getattr(xattr, 'xattr', None) is None:
raise ImportError("wrong xattr package imported")
-+from twisted.python import log
-+from twisted.python.failure import Failure
++from twisted.python.util import untilConcludes
+ from twisted.python import log
from twisted.web2 import responsecode
from twisted.web2.http import HTTPError, StatusResponse
from twisted.web2.dav import davxml
+from twisted.web2.dav.http import statusForFailure
++
class xattrPropertyStore (object):
"""
-@@ -66,16 +75,8 @@
- deadPropertyXattrPrefix = "user."
- def _encode(clazz, name):
-- #
-- # FIXME: The xattr API in Mac OS 10.4.2 breaks if you have "/" in an
-- # attribute name (radar://4202440). We'll quote the strings to get rid
-- # of "/" characters for now.
-- #
-- result = list("{%s}%s" % name)
-- for i in range(len(result)):
-- c = result[i]
-- if c in "%/": result[i] = "%%%02X" % (ord(c),)
-- r = clazz.deadPropertyXattrPrefix + ''.join(result)
-+ result = urllib.quote("{%s}%s" % name, safe='{}:')
-+ r = clazz.deadPropertyXattrPrefix + result
- return r
+@@ -94,59 +100,109 @@
+ self.attrs = xattr.xattr(self.resource.fp.path)
- def _decode(clazz, name):
-@@ -97,19 +98,83 @@
-
def get(self, qname):
++ """
++ Retrieve the value of a property stored as an extended attribute on the
++ wrapped path.
++
++ @param qname: The property to retrieve as a two-tuple of namespace URI
++ and local name.
++
++ @raise HTTPError: If there is no value associated with the given
++ property.
++
++ @return: A L{WebDAVDocument} representing the value associated with the
++ given property.
++ """
try:
-- value = self.attrs[self._encode(qname)]
-+ data = self.attrs[self._encode(qname)]
+ data = self.attrs[self._encode(qname)]
+- try:
+- value = decompress(data)
+- except zlib.error:
+- # Value is not compressed; data was stored by old
+- # code. This is easy to handle, so let's keep
+- # compatibility here.
+- value = data
+- del data
except KeyError:
raise HTTPError(StatusResponse(
responsecode.NOT_FOUND,
"No such property: {%s}%s" % qname
))
-+ except Exception, e:
-+ raise HTTPError(StatusResponse(
-+ statusForFailure(Failure()),
-+ "Unable to read property: {%s}%s" % qname
-+ ))
++ except:
++ # XXX Untested, I can't get the getitem to actually fail in a test
++ # environment. -exarkun
++ raise HTTPError(
++ StatusResponse(
++ statusForFailure(Failure()),
++ "Unable to read property: " + key))
-- doc = davxml.WebDAVDocument.fromString(value)
+ #
-+ # Serialize XML data to an xattr. The storage format has
-+ # changed over time:
++ # Unserialize XML data from an xattr. The storage format has changed
++ # over time:
+ #
+ # 1- Started with XML
+ # 2- Started compressing the XML due to limits on xattr size
@@ -77,93 +83,85 @@
+ # ones for compatibility.
+ #
+ legacy = False
-
-- return doc.root_element
-+ try:
++
+ try:
+- doc = davxml.WebDAVDocument.fromString(value)
+ data = decompress(data)
+ except zlib.error:
+ legacy = True
+- return doc.root_element
+ try:
+ doc = davxml.WebDAVDocument.fromString(data)
-+ result = doc.root_element
-+ except ValueError:
-+ legacy = True
-+
-+ try:
-+ result = unpickle(data)
+ except ValueError:
+- msg = "Invalid property value stored on server: {%s}%s %s" % (qname[0], qname[1], value)
+- log.err(msg)
+- raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, msg))
+-
+- def set(self, property):
+- for n in range(20):
+ try:
+- self.attrs[self._encode(property.qname())] = compress(property.toxml())
+- except IOError, error:
+- if error.errno != EAGAIN:
+- raise
+- sleep(random() / 10) # OMG Brutal Hax
++ doc = unpickle(data)
+ except UnpicklingError:
-+ msg = ("Invalid property value stored on server: {%s}%s %s"
-+ % (qname[0], qname[1], data))
-+ log.err(msg)
-+ raise HTTPError(StatusResponse(responsecode.INTERNAL_SERVER_ERROR, msg))
-+
++ format = "Invalid property value stored on server: {%s}%s %s"
++ msg = format % (qname[0], qname[1], data)
++ log.err(None, msg)
++ raise HTTPError(
++ StatusResponse(responsecode.INTERNAL_SERVER_ERROR, msg))
+ else:
+- break
+-
++ legacy = True
+
+ if legacy:
-+ # Try to upgrade the property to the current format
-+ try:
-+ log.msg("Upgrading property %r on resource %r"
-+ % (qname, self.resource.fp.path))
-+ self.set(result)
-+ except Exception, e:
-+ #
-+ # Hrm, that failed. No need to re-raise here, since
-+ # we can do what we were asked to do, but let's
-+ # complain about it.
-+ #
-+ log.err("Error while upgrading property %r on resource %r: %s"
-+ % (qname, self.resource.fp.path, e))
++ self.set(doc.root_element)
+
-+ return result
++ return doc.root_element
+
- def set(self, property):
-- self.attrs[self._encode(property.qname())] = property.toxml()
-+ for n in range(20):
-+ data = compress(property.toxml())
-+ try:
-+ self.attrs[self._encode(property.qname())] = data
-+ except Exception, e:
-+ if e.errno == EAGAIN and n < 19:
-+ sleep(random() / 10) # OMG Brutal Hax
-+ else:
-+ raise HTTPError(StatusResponse(
-+ statusForFailure(Failure()),
-+ "Unable to write property: %s" % (property.sname(),)
-+ ))
-+ else:
-+ break
-
++
++ def set(self, property):
++ key = self._encode(property.qname())
++ value = compress(property.toxml())
++ untilConcludes(setitem, self.attrs, key, value)
++
# Update the resource because we've modified it
self.resource.fp.restat()
-@@ -121,15 +186,31 @@
+
+ def delete(self, qname):
++ key = self._encode(qname)
+ try:
+- del(self.attrs[self._encode(qname)])
+- except KeyError:
++ del self.attrs[key]
++ except KeyError, e:
# RFC 2518 Section 12.13.1 says that removal of
# non-existing property is not an error.
pass
-+ except Exception, e:
-+ raise HTTPError(StatusResponse(
-+ statusForFailure(Failure()),
-+ "Unable to delete property: {%s}%s" % qname
-+ ))
++ except:
++ # XXX Untested, I can't get the delitem to actually fail in a test
++ # environment. -exarkun
++ raise HTTPError(
++ StatusResponse(
++ statusForFailure(Failure()),
++ "Unable to delete property: " + key))
def contains(self, qname):
try:
return self._encode(qname) in self.attrs
except TypeError:
return False
-+ except Exception, e:
-+ raise HTTPError(StatusResponse(
-+ statusForFailure(Failure()),
-+ "Unable to read properties"
-+ ))
++ except:
++ # XXX Untested, I can't get the __contains__ to actually fail in a
++ # test environment. -exarkun
++ raise HTTPError(
++ StatusResponse(
++ statusForFailure(Failure()),
++ "Unable to read property: " + key))
def list(self):
prefix = self.deadPropertyXattrPrefix
- prefix_len = len(prefix)
-
-- return [ self._decode(name) for name in self.attrs if name.startswith(prefix) ]
-+ try:
-+ return [ self._decode(name) for name in self.attrs if name.startswith(prefix) ]
-+ except Exception, e:
-+ raise HTTPError(StatusResponse(
-+ statusForFailure(Failure()),
-+ "Unable to list properties"
-+ ))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20090316/90d54e55/attachment-0001.html>
More information about the calendarserver-changes
mailing list