[CalendarServer-changes] [5017] CalendarServer/trunk/txdav
source_changes at macosforge.org
source_changes at macosforge.org
Tue Feb 2 13:36:57 PST 2010
Revision: 5017
http://trac.macosforge.org/projects/calendarserver/changeset/5017
Author: wsanchez at apple.com
Date: 2010-02-02 13:36:56 -0800 (Tue, 02 Feb 2010)
Log Message:
-----------
Add xattr property store
Added Paths:
-----------
CalendarServer/trunk/txdav/propertystore/
CalendarServer/trunk/txdav/propertystore/__init__.py
CalendarServer/trunk/txdav/propertystore/base.py
CalendarServer/trunk/txdav/propertystore/test/
CalendarServer/trunk/txdav/propertystore/test/__init__.py
CalendarServer/trunk/txdav/propertystore/test/test_xattr.py
CalendarServer/trunk/txdav/propertystore/xattr.py
Copied: CalendarServer/trunk/txdav/propertystore/__init__.py (from rev 4999, CalendarServer/trunk/txdav/__init__.py)
===================================================================
--- CalendarServer/trunk/txdav/propertystore/__init__.py (rev 0)
+++ CalendarServer/trunk/txdav/propertystore/__init__.py 2010-02-02 21:36:56 UTC (rev 5017)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+WebDAV property support for Twisted.
+"""
Added: CalendarServer/trunk/txdav/propertystore/base.py
===================================================================
--- CalendarServer/trunk/txdav/propertystore/base.py (rev 0)
+++ CalendarServer/trunk/txdav/propertystore/base.py 2010-02-02 21:36:56 UTC (rev 5017)
@@ -0,0 +1,169 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Base property store.
+"""
+
+__all__ = [
+ "AbstractPropertyStore",
+]
+
+from zope.interface import implements
+
+from twext.log import LoggingMixIn
+
+from txdav.idav import IPropertyStore, IPropertyName
+
+
+class PropertyName(LoggingMixIn):
+ """
+ Property name.
+ """
+ implements(IPropertyName)
+
+ @staticmethod
+ def fromString(sname):
+ index = sname.find("}")
+
+ if (index is -1 or not len(sname) > index or not sname[0] == "{"):
+ raise TypeError("Invalid sname: %r" % (sname,))
+
+ return (sname[1:index], sname[index+1:])
+
+ def __init__(self, namespace, name):
+ self.namespace = namespace
+ self.name = name
+
+ def __hash__(self):
+ return hash((self.namespace, self.name))
+
+ def __repr__(self):
+ return "<%s: %s>" % (
+ self.__class__.__name__,
+ self.toString(),
+ )
+
+ def toString(self):
+ return "{%s}%s" % (self.namespace, self.name)
+
+
+class AbstractPropertyStore(LoggingMixIn):
+ """
+ Base property store.
+ """
+ implements(IPropertyStore)
+
+ #
+ # Subclasses must override these
+ #
+
+ def __delitem__(self, key):
+ raise NotImplementedError()
+
+ def __getitem__(self, key):
+ raise NotImplementedError()
+
+ def __contains__(self, key):
+ raise NotImplementedError()
+
+ def __setitem__(key, value):
+ raise NotImplementedError()
+
+ def __iter__(self):
+ raise NotImplementedError()
+
+ def __len__(self):
+ raise NotImplementedError()
+
+ def flush(self):
+ raise NotImplementedError()
+
+ def abort(self):
+ raise NotImplementedError()
+
+ #
+ # Subclasses may override these
+ #
+
+ def len(self, key):
+ return self.__len__(key)
+
+ def clear(self):
+ for key in self.__iter__():
+ self.__delitem__(key)
+
+ def get(self, key, default=None):
+ if self.__contains__(key):
+ return self.__getitem__(key)
+ else:
+ return default
+
+ def iter(self):
+ return self.__iter__()
+
+ def iteritems(self):
+ return (
+ (key, self.get(key))
+ for key in self.__iter__()
+ )
+
+ def items(self):
+ return list(self.iteritems())
+
+ iterkeys = iter
+ __iterkeys__ = iter
+
+ def keys(self):
+ return tuple(self.__iter__())
+
+ def itervalues(self):
+ return (
+ self.get(key)
+ for key in self.__iter__()
+ )
+
+ def values(self):
+ return list(self.itervalues())
+
+ def pop(self, key, default=None):
+ try:
+ value = self.__getitem__(key)
+ except KeyError:
+ if default is None:
+ raise
+ return default
+
+ self.__delitem__(key)
+
+ return value
+
+ def popitem(self):
+ for key in self.__iter__():
+ self.__delitem__(key)
+ break
+
+ def setdefault(self, key, default=None):
+ if self.__contains__(key):
+ return key
+
+ self.__setitem__(key, default)
+
+ return default
+
+ def update(other=None):
+ # FIXME
+ raise NotImplementedError()
Copied: CalendarServer/trunk/txdav/propertystore/test/__init__.py (from rev 4999, CalendarServer/trunk/txdav/__init__.py)
===================================================================
--- CalendarServer/trunk/txdav/propertystore/test/__init__.py (rev 0)
+++ CalendarServer/trunk/txdav/propertystore/test/__init__.py 2010-02-02 21:36:56 UTC (rev 5017)
@@ -0,0 +1,19 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Property store tests.
+"""
Added: CalendarServer/trunk/txdav/propertystore/test/test_xattr.py
===================================================================
--- CalendarServer/trunk/txdav/propertystore/test/test_xattr.py (rev 0)
+++ CalendarServer/trunk/txdav/propertystore/test/test_xattr.py 2010-02-02 21:36:56 UTC (rev 5017)
@@ -0,0 +1,52 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Property store tests.
+"""
+
+from zope.interface.verify import verifyObject, BrokenMethodImplementation
+
+#from twisted.python.filepath import FilePath
+from twisted.trial import unittest
+
+from txdav.idav import IPropertyStore
+from txdav.propertystore.xattr import PropertyStore
+
+
+class PropertyStoreTest(unittest.TestCase):
+ def test_interface(self):
+ raise NotImplementedError()
+
+ store = PropertyStore()
+
+ try:
+ verifyObject(IPropertyStore, store)
+ except BrokenMethodImplementation, e:
+ self.fail(e)
+ test_interface.todo = "Unimplemented"
+
+ def test_init(self):
+ raise NotImplementedError()
+ test_init.todo = "Unimplemented"
+
+ def test_flush(self):
+ raise NotImplementedError()
+ test_flush.todo = "Unimplemented"
+
+ def test_abort(self):
+ raise NotImplementedError()
+ test_abort.todo = "Unimplemented"
Added: CalendarServer/trunk/txdav/propertystore/xattr.py
===================================================================
--- CalendarServer/trunk/txdav/propertystore/xattr.py (rev 0)
+++ CalendarServer/trunk/txdav/propertystore/xattr.py 2010-02-02 21:36:56 UTC (rev 5017)
@@ -0,0 +1,207 @@
+##
+# Copyright (c) 2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Property store using filesystem extended attributes.
+"""
+
+from __future__ import absolute_import
+
+__all__ = [
+ "PropertyStore",
+]
+
+import sys
+import errno
+import urllib
+from zlib import compress, decompress, error as ZlibError
+from cPickle import UnpicklingError, loads as unpickle
+
+import xattr
+
+if getattr(xattr, "xattr", None) is None:
+ raise ImportError("wrong xattr package imported")
+
+from twisted.web2.dav.davxml import WebDAVDocument
+
+from txdav.propertystore.base import AbstractPropertyStore, PropertyName
+from txdav.idav import PropertyStoreError
+
+
+#
+# RFC 2518 Section 12.13.1 says that removal of non-existing property
+# is not an error. python-xattr on Linux fails with ENODATA in this
+# case. On OS X, the xattr library fails with ENOATTR, which CPython
+# does not expose. Its value is 93.
+#
+if sys.platform is "darwin":
+ if hasattr(errno, "ENOATTR"):
+ _ERRNO_NO_ATTR = errno.ENOATTR
+ else:
+ _ERRNO_NO_ATTR = 93
+else:
+ _ERRNO_NO_ATTR = errno.ENODATA
+
+
+class PropertyStore(AbstractPropertyStore):
+ """
+ Property store using filesystem extended attributes.
+
+ This implementation uses Bob Ippolito's xattr package, available from::
+
+ http://undefined.org/python/#xattr
+ """
+ #
+ # Dead properties are stored as extended attributes on disk. In order to
+ # avoid conflicts with other attributes, prefix dead property names.
+ #
+ deadPropertyXattrPrefix = "WebDAV:"
+
+ # Linux seems to require that attribute names use a "user." prefix.
+ if sys.platform == "linux2":
+ deadPropertyXattrPrefix = "user.%s" % (deadPropertyXattrPrefix,)
+
+ @classmethod
+ def _encodeKey(cls, name):
+ result = urllib.quote(name.toString(), safe="{}:")
+ r = cls.deadPropertyXattrPrefix + result
+ return r
+
+ @classmethod
+ def _decodeKey(cls, name):
+ return PropertyName.fromString(
+ urllib.unquote(name[len(cls.deadPropertyXattrPrefix):])
+ )
+
+ def __init__(self, path):
+ self.path = path
+ self.attrs = xattr.xattr(path.path)
+ self.removed = set()
+ self.modified = {}
+
+ def __str__(self):
+ return "<%s %s>" % (self.__class__.__name__, self.path.path)
+
+ #
+ # Accessors
+ #
+
+ def __delitem__(self, key):
+ if key in self.modified:
+ del self.modified[key]
+ self.removed.add(key)
+
+ def __getitem__(self, key):
+ if key in self.modified:
+ return self.modified[key]
+
+ if key in self.removed:
+ raise KeyError(key)
+
+ try:
+ data = self.attrs[self._encodeKey(key)]
+ except IOError, e:
+ if e.errno in _ERRNO_NO_ATTR:
+ raise KeyError(key)
+ raise PropertyStoreError(e)
+
+ #
+ # 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
+ # 3- Switched to pickle which is faster, still compressing
+ # 4- Back to compressed XML for interoperability, size
+ #
+ # We only write the current format, but we also read the old
+ # ones for compatibility.
+ #
+ legacy = False
+
+ try:
+ data = decompress(data)
+ except ZlibError:
+ legacy = True
+
+ try:
+ doc = WebDAVDocument.fromString(data)
+ except ValueError:
+ try:
+ doc = unpickle(data)
+ except UnpicklingError:
+ msg = "Invalid property value stored on server: %s %s" % (
+ key.toString(), data
+ )
+ self.log_error(msg)
+ raise PropertyStoreError(msg)
+ else:
+ legacy = True
+
+ if legacy:
+ self.set(doc.root_element)
+
+ return doc.root_element
+
+ def __contains__(self, key):
+ if key in self.modified:
+ return True
+ if key in self.removed:
+ return False
+ return self._encodeKey(key) in self.attrs
+
+ def __setitem__(self, key, value):
+ if key in self.removed:
+ self.removed.remove(key)
+ self.modified[key] = value
+
+ def __iter__(self):
+ seen = set()
+
+ for key in self.attrs:
+ key = self._decodeKey(key)
+ if key not in self.removed:
+ seen.add(key)
+ yield key
+
+ for key in self.modified:
+ if key not in seen:
+ yield key
+
+ def __len__(self):
+ return len(self.attrs)
+
+ #
+ # I/O
+ #
+
+ def flush(self):
+ attrs = self.attrs
+ removed = self.removed
+ modified = self.modified
+
+ for key in removed:
+ assert key not in modified
+ del attrs[self._encodeKey(key)]
+
+ for key in modified:
+ assert key not in removed
+ value = modified[key]
+ attrs[self._encodeKey(key)] = compress(value.toxml())
+
+ def abort(self):
+ self.removed.clear()
+ self.modified.clear()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100202/429ebb74/attachment-0001.html>
More information about the calendarserver-changes
mailing list