[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