[CalendarServer-changes] [2568] CalendarServer/branches/users/cdaboo/sqlpropstore-2563

source_changes at macosforge.org source_changes at macosforge.org
Mon Jun 16 20:54:14 PDT 2008


Revision: 2568
          http://trac.macosforge.org/projects/calendarserver/changeset/2568
Author:   cdaboo at apple.com
Date:     2008-06-16 20:54:13 -0700 (Mon, 16 Jun 2008)

Log Message:
-----------
Reworked sql property store to be a more direct replacement of xattrs.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.static.patch
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/__init__.py
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/extensions.py
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/root.py
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sql.py
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/static.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sqlprops.py

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2008-06-17 03:54:13 UTC (rev 2568)
@@ -53,16 +53,26 @@
          yield destparent
          destparent = destparent.getResult()
  
-@@ -144,7 +155,19 @@
+@@ -144,10 +155,30 @@
          log.err(msg)
          raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, msg))
  
 -    x = waitForDeferred(move(self.fp, request.uri, destination.fp, destination_uri, depth))
+-    yield x
+-    yield x.getResult()
 +    # Lets optimise a move within the same directory to a new resource as a simple move
 +    # rather than using the full transaction based storeResource api. This allows simple
 +    # "rename" operations to work quickly.
 +    if (not destination.exists()) and destparent == parent:
 +        x = waitForDeferred(move(self.fp, request.uri, destination.fp, destination_uri, depth))
++        yield x
++        result = x.getResult()
+ 
++        # Explicitly move properties - no one else does it
++        destination.deadProperties().copy(self.deadProperties())
++        self.deadProperties().deleteAll()
++
++        yield result
 +    else:
 +        x = waitForDeferred(put_common.storeResource(request,
 +                                                     source=self,
@@ -71,6 +81,9 @@
 +                                                     destination_uri=destination_uri,
 +                                                     deletesource=True,
 +                                                     depth=depth))
-     yield x
-     yield x.getResult()
++        yield x
++        yield x.getResult()
++
+ http_MOVE = deferredGenerator(http_MOVE)
  
+ def prepareForCopy(self, request):

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2008-06-17 03:54:13 UTC (rev 2568)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/method/delete.py	(revision 19773)
 +++ twisted/web2/dav/method/delete.py	(working copy)
-@@ -58,8 +58,28 @@
+@@ -58,8 +58,31 @@
      yield x
      x.getResult()
  
@@ -23,6 +23,9 @@
 -    yield x.getResult()
 +    result = x.getResult()
  
++    # Explicitly delete properties - no one else does it
++    self.deadProperties().deleteAll()
++
 +    # Adjust quota
 +    if myquota is not None:
 +        d = waitForDeferred(self.quotaSizeAdjust(request, -old_size))

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2008-06-17 03:54:13 UTC (rev 2568)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/method/put_common.py	(revision 0)
 +++ twisted/web2/dav/method/put_common.py	(revision 0)
-@@ -0,0 +1,266 @@
+@@ -0,0 +1,273 @@
 +##
 +# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
 +#
@@ -211,6 +211,10 @@
 +        yield response
 +        response = response.getResult()
 +
++        # Copy dead properties first, before adding overridden values
++        if source is not None:
++            destination.deadProperties().copy(source.deadProperties())
++
 +        # Update the MD5 value on the resource
 +        if source is not None:
 +            # Copy MD5 value from source to destination
@@ -256,6 +260,9 @@
 +            delete(source_uri, source.fp, depth)
 +            rollback.source_deleted = True
 +
++            # Explicitly delete properties - no one else does it
++            source.deadProperties().deleteAll()
++
 +        # Can now commit changes and forget the rollback details
 +        rollback.Commit()
 +

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.dav.xattrprops.patch	2008-06-17 03:54:13 UTC (rev 2568)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/xattrprops.py	(revision 19773)
 +++ twisted/web2/dav/xattrprops.py	(working copy)
-@@ -33,15 +33,24 @@
+@@ -33,12 +33,19 @@
  
  import urllib
  import sys
@@ -19,15 +19,10 @@
      raise ImportError("wrong xattr package imported")
  
 +from twisted.python import log
-+from twisted.python.failure import Failure
  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 @@
+@@ -66,16 +73,8 @@
          deadPropertyXattrPrefix = "user."
  
      def _encode(clazz, name):
@@ -46,7 +41,7 @@
          return r
  
      def _decode(clazz, name):
-@@ -97,19 +98,86 @@
+@@ -97,19 +96,74 @@
  
      def get(self, qname):
          try:
@@ -57,13 +52,6 @@
                  responsecode.NOT_FOUND,
                  "No such property: {%s}%s" % qname
              ))
-+        except Exception, e:
-+            log.error("Unable to read property {%s}%s: %s"
-+                      % (qname[0], qname[1], e))
-+            raise HTTPError(StatusResponse(
-+                statusForFailure(Failure()),
-+                "Unable to read property: {%s}%s" % qname
-+            ))
  
 -        doc = davxml.WebDAVDocument.fromString(value)
 +        #
@@ -123,53 +111,31 @@
 +            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:
-+                    log.error("Unable to write property %s: %s" % (property.sname(), e))
-+                    raise HTTPError(StatusResponse(
-+                        statusForFailure(Failure()),
-+                        "Unable to write property: %s" % (property.sname(),)
-+                    ))
++            except IOError, error:
++                if error.errno != EAGAIN:
++                    raise
++                sleep(random() / 10) # OMG Brutal Hax
 +            else:
 +                break
  
          # Update the resource because we've modified it
          self.resource.fp.restat()
-@@ -121,15 +189,34 @@
-             # RFC 2518 Section 12.13.1 says that removal of
+@@ -122,6 +176,10 @@
              # non-existing property is not an error.
              pass
-+        except Exception, e:
-+            log.error("Unable to delete property {%s}%s: %s" % (qname[0], qname[1], e))
-+            raise HTTPError(StatusResponse(
-+                statusForFailure(Failure()),
-+                "Unable to delete property: {%s}%s" % qname
-+            ))
  
++    def deleteAll(self):
++        # xattrs deleted when the underlying file is deleted.
++        pass
++
      def contains(self, qname):
          try:
              return self._encode(qname) in self.attrs
-         except TypeError:
-             return False
-+        except Exception, e:
-+            log.error("Unable to read properties: %s" % (e,))
-+            raise HTTPError(StatusResponse(
-+                statusForFailure(Failure()),
-+                "Unable to read properties"
-+            ))
- 
-     def list(self):
-         prefix     = self.deadPropertyXattrPrefix
+@@ -133,3 +191,7 @@
          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:
-+            log.error("Unable to list properties: %s" % (e,))
-+            raise HTTPError(StatusResponse(
-+                statusForFailure(Failure()),
-+                "Unable to list properties"
-+            ))
+         return [ self._decode(name) for name in self.attrs if name.startswith(prefix) ]
++
++    def copy(self, props):
++        for item in props.list():
++            self.set(props.get(item))

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.static.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.static.patch	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/lib-patches/Twisted/twisted.web2.static.patch	2008-06-17 03:54:13 UTC (rev 2568)
@@ -2,15 +2,16 @@
 ===================================================================
 --- twisted/web2/static.py	(revision 19773)
 +++ twisted/web2/static.py	(working copy)
-@@ -200,7 +200,10 @@
+@@ -200,7 +200,11 @@
          super(File, self).__init__()
  
          self.putChildren = {}
 -        self.fp = filepath.FilePath(path)
-+        if isinstance(path, FilePath):
-+            self.fp = path
-+        else:
-+            self.fp = filepath.FilePath(path)
++        if isinstance(path, filepath.FilePath): 
++            self.fp = path 
++        else: 
++            self.fp = filepath.FilePath(path) 
++        
          # Remove the dots from the path to split
          self.defaultType = defaultType
          self.ignoredExts = list(ignoredExts)

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/__init__.py	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/__init__.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -47,6 +47,7 @@
     "root",
     "schedule",
     "sql",
+    "sqlprops",
     "static",
 ]
 

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/extensions.py	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/extensions.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -49,10 +49,11 @@
 from twisted.web2.dav.util import joinURL
 from twisted.web2.dav.xattrprops import xattrPropertyStore
 
+from twistedcaldav.directory.directory import DirectoryService
+from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.log import Logger, LoggingMixIn
+from twistedcaldav.sqlprops import sqlPropertyStore
 from twistedcaldav.util import submodule, Alternator, printTracebacks
-from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.directory.directory import DirectoryService
 
 log = Logger()
 
@@ -448,6 +449,12 @@
     """
     Extended L{twisted.web2.dav.static.DAVFile} implementation.
     """
+
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = sqlPropertyStore(self)
+        return self._dead_properties
+
     def readProperty(self, property, request):
         if type(property) is tuple:
             qname = property

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/method/put_common.py	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/method/put_common.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twisted.web2.dav.fileop import copy
 
 """
 PUT/COPY/MOVE common behavior.
@@ -515,7 +516,7 @@
 
         # Do put or copy based on whether source exists
         if source is not None:
-            response = maybeDeferred(copyWithXAttrs, source.fp, destination.fp, destination_uri)
+            response = maybeDeferred(copy, source.fp, destination.fp, destination_uri, "0")
         else:
             md5 = MD5StreamWrapper(MemoryStream(calendardata))
             response = maybeDeferred(putWithXAttrs, md5, destination.fp)
@@ -523,6 +524,10 @@
         yield response
         response = response.getResult()
 
+        # Copy dead properties first, before adding overridden values
+        if source is not None:
+            destination.deadProperties().copy(source.deadProperties())
+
         # Update the MD5 value on the resource
         if source is not None:
             # Copy MD5 value from source to destination
@@ -598,6 +603,9 @@
             rollback.source_deleted = True
             log.debug("Source removed %s" % (source.fp.path,))
 
+            # Explicitly delete properties - no one else does it
+            source.deadProperties().deleteAll()
+
         def doSourceIndexRecover():
             """
             Do source resource indexing. This only gets called when restoring

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/root.py	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/root.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -31,6 +31,7 @@
 from twistedcaldav.log import Logger
 from twistedcaldav.static import CalendarHomeFile
 from twistedcaldav.directory.principal import DirectoryPrincipalResource
+from twistedcaldav.sqlprops import sqlPropertyStore
 
 log = Logger()
 
@@ -70,7 +71,8 @@
 
     def deadProperties(self):
         if not hasattr(self, '_dead_properties'):
-            self._dead_properties = CachingXattrPropertyStore(self)
+            self._dead_properties = sqlPropertyStore(self)
+            #self._dead_properties = CachingXattrPropertyStore(self)
 
         return self._dead_properties
 

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sql.py	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sql.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -40,7 +40,7 @@
     A generic SQL database.
     """
 
-    def __init__(self, dbpath, persistent, autocommit=False):
+    def __init__(self, dbpath, persistent, autocommit=False, utf8=False):
         """
         
         @param dbpath: the path where the db file is stored.
@@ -50,10 +50,14 @@
         @type persistent: bool
         @param autocommit: C{True} if auto-commit mode is desired, C{False} otherwise
         @type autocommit: bool
+        @param utf8: C{True} if utf8 encoded C{str} should be returned for SQl TEXT data,
+            C{False} if C{unicode} should be returned.
+        @type utf8: bool
         """
         self.dbpath = dbpath
         self.persistent = persistent
         self.autocommit = autocommit
+        self.utf8 = utf8
 
     def _db_version(self):
         """
@@ -82,6 +86,8 @@
             except:
                 log.err("Unable to open database: %s" % (db_filename,))
                 raise
+            if self.utf8:
+                self._db_connection.text_factory = str
 
             #
             # Set up the schema

Added: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sqlprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sqlprops.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/sqlprops.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -0,0 +1,422 @@
+##
+# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+##
+from twistedcaldav.log import Logger
+
+"""
+DAV Property store an sqlite database.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = ["sqlPropertyStore"]
+
+import cPickle
+import os
+
+from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError, StatusResponse
+
+from twistedcaldav.sql import AbstractSQLDatabase
+
+log = Logger()
+
+class sqlPropertyStore (object):
+    """
+    A dead property store that uses an SQLite database back end.
+    """
+ 
+    def __init__(self, resource):
+        self.resource = resource
+        if os.path.exists(os.path.dirname(resource.fp.path)):
+            from twistedcaldav.root import RootResource
+            if resource.isCollection() and isinstance(resource, RootResource):
+                self.rname = ""
+                indexpath = resource.fp.path
+            else:
+                self.rname = os.path.basename(resource.fp.path)
+                indexpath = os.path.dirname(resource.fp.path)
+            self.index = SQLPropertiesDatabase(indexpath)
+            if resource.isCollection():
+                self.childindex = SQLPropertiesDatabase(resource.fp.path)
+            else:
+                self.childindex = None
+        else:
+            log.err("No sqlPropertyStore file for %s" % (os.path.dirname(resource.fp.path),))
+            self.index = None
+            self.childindex = None
+
+    def get(self, qname):
+        """
+        Read property from index.
+        
+        @param qname: C{tuple} of property namespace and name.
+        """
+        if not self.index:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No such property: {%s}%s" % qname
+            ))
+            
+        value = self.index.getOnePropertyValue(self.rname, qname)
+        if not value:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No such property: {%s}%s" % qname
+            ))
+            
+        return value
+
+    def _getAll(self, hidden=False):
+        """
+        Read all properties from index.
+        
+        @param hidden: C{True} to return hidden properties, C{False} otherwise.
+        @return: a C{dict} containing property name/value.
+        """
+        if not self.index:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No properties"
+            ))
+            
+        return self.index.getAllPropertyValues(self.rname, hidden)
+
+    def set(self, property):
+        """
+        Write property into index.
+        
+        @param property: C{Property} to write
+        """
+
+        if self.index:
+            self.index.setOnePropertyValue(self.rname, property.qname(), property, property.hidden)
+
+    def _setSeveral(self, properties):
+        """
+        Write specific properties into index.
+        
+        @param properties: C{list} of properties to write
+        """
+
+        if self.index:
+            self.index.setSeveralPropertyValues(self.rname, [(p.qname(), p, p.hidden) for p in properties])
+
+    def _setAll(self, properties):
+        """
+        Write all properties into index.
+        
+        @param properties: C{list} of properties to write
+        """
+
+        if self.index:
+            self.index.removeAll(self.rname)
+            self.index.setSeveralPropertyValues(self.rname, [(p.qname(), p, p.hidden) for p in properties])
+
+    def delete(self, qname):
+        """
+        Delete property from index.
+
+        DELETE from PROPERTIES where NAME=<<rname>> and PROPNAME=<<pname>>
+
+        @param qname:
+        """
+        
+        if self.index:
+            self.index.removeProperty(self.rname, qname)
+
+    def deleteAll(self):
+        """
+        Delete all properties from index.
+
+        DELETE from PROPERTIES where NAME=<<rname>>
+        """
+        
+        if self.index:
+            self.index.removeAllProperties(self.rname)
+
+    def contains(self, qname):
+        if self.index:
+            value = self.index.getOnePropertyValue(self.rname, qname)
+            return value is not None
+        else:
+            return False
+
+    def list(self):
+        """
+        List all property names for this resource.
+        
+        SELECT PROPNAME from PROPERTIES where NAME=<<rname>>
+        
+        """
+
+        if self.index:
+            return self.index.listProperties(self.rname)
+        else:
+            return ()
+
+    def copy(self, props):
+        """
+        Copy properties from another property store into this one, replacing everything
+        currently present.
+        
+        @param props: a property store to copy from
+        @type props: C{sqlPropertyStore}
+        """
+        
+        oldprops = props._getAll(hidden=True)
+        self._setAll(oldprops)
+        self.cache = {}
+
+class SQLPropertiesDatabase(AbstractSQLDatabase):
+    """
+    A database to store properties on resources in a collection.
+    It maintains a cache of values read from the database.
+
+    SCHEMA:
+    
+    Properties Database:
+    
+    ROW: RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT, PROPERTYVALUE
+    
+    """
+    
+    dbType = "SQLPROPERTIES"
+    dbFilename = ".db.sqlproperties"
+    dbFormatVersion = "1"
+
+    @classmethod
+    def _encode(cls, name):
+        return "{%s}%s" % name
+
+    @classmethod
+    def _decode(cls, name):
+        index = name.find("}")
+    
+        if (index is -1 or not len(name) > index or not name[0] == "{"):
+            raise ValueError("Invalid encoded name: %r" % (name,))
+    
+        return (name[1:index], name[index+1:])
+
+    def __init__(self, path):
+        path = os.path.join(path, SQLPropertiesDatabase.dbFilename)
+        super(SQLPropertiesDatabase, self).__init__(path, True, utf8=True)
+        
+        self.cache = {}
+
+    def getOnePropertyValue(self, rname, pname):
+        """
+        Get a property.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pname: a C{str} containing the name of the property to get.
+        @return: an object representing the property.
+        """
+        
+        # Check cache first
+        if not self.cache.has_key((rname, pname)):
+            # Get the property
+            log.debug("getPropertyValue: %s \"%s\" \"%s\"" % (self.dbpath, rname, pname))
+            members = []
+            for row in self._db_execute("select PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1 and PROPERTYNAME = :2", rname, self._encode(pname)):
+                members.append(row[0])
+            setlength =  len(members)
+            if setlength == 0:
+                value = None
+            elif setlength == 1:
+                value = cPickle.loads(members[0])
+            else:
+                raise ValueError("Multiple properties of the same name \"%s\" stored for resource \"%s\"" % (pname, rname,))
+
+            self.cache[(rname, pname)] = value
+        
+        return self.cache[(rname, pname)]
+
+    def getAllPropertyValues(self, rname, hidden):
+        """
+        Get specified property values from specific resource.
+    
+        @param rname: a C{str} containing the resource name.
+        @param hidden: C{True} to return hidden properties, C{False} otherwise.
+        @return: a C{dict} containing property name/value.
+        """
+        
+        properties = {}
+        statement = "select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1"
+        if not hidden:
+            statement += " and HIDDEN = 'F'"
+        for row in self._db_execute(statement, rname):
+            properties[self._decode(row[0])] = cPickle.loads(row[1])
+
+        return properties
+
+    def setOnePropertyValue(self, rname, pname, pvalue, hidden):
+        """
+        Add a property.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pname: a C{str} containing the name of the property to set.
+        @param pvalue: the property to set.
+        @param hidden: C{True} for a hidden property, C{False} otherwise.
+        """
+        
+        # Remove what is there, then add it back.
+        self._delete_from_db(rname, self._encode(pname))
+        self._add_to_db(rname, self._encode(pname), cPickle.dumps(pvalue), pvalue.toxml(), hidden)
+        self._db_commit()
+        
+        self.cache[(rname, pname)] = pvalue 
+
+    def setSeveralPropertyValues(self, rname, properties):
+        """
+        Add a set of properties.
+    
+        @param rname: a C{str} containing the resource name.
+        @param properties: a C{dict} containing the name of the properties to set.
+        """
+        
+        # Remove what is there, then add it back.
+        for p in properties:
+            self._delete_from_db(rname, self._encode(p[0]))
+            self._add_to_db(rname, self._encode(p[0]), cPickle.dumps(p[1]), p[1].toxml(), p[2])
+        self._db_commit()
+
+    def removeProperty(self, rname, pname):
+        """
+        Remove a property.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pname: a C{str} containing the name of the property to remove.
+        """
+
+        self._delete_from_db(rname, self._encode(pname))
+        self._db_commit()
+        
+        try:
+            del self.cache[(rname, pname)]
+        except KeyError:
+            pass
+
+    def removeAllProperties(self, rname):
+        """
+        Remove all properties for resource.
+    
+        @param rname: a C{str} containing the resource name.
+        """
+
+        self._delete_all_from_db(rname)
+        self._db_commit()
+
+    def listProperties(self, rname):
+        """
+        List all properties in resource.
+    
+        @param rname: a C{str} containing the resource name.
+        @return: a C{set} containing the property names.
+        """
+
+        members = set()
+        for row in self._db_execute("select PROPERTYNAME from PROPERTIES where RESOURCENAME = :1", rname):
+            members.add(self._decode(row[0]))
+        return members
+
+    def _add_to_db(self, rname, pname, pobject, pvalue, hidden):
+        """
+        Add a property.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pname: a C{str} containing the name of the property to set.
+        @param pobject: a C{str} containing the pickled representation of the property object.
+        @param pvalue: a C{str} containing the text of the property value to set.
+        """
+        
+        self._db_execute(
+            """
+            insert into PROPERTIES (RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT, PROPERTYVALUE, HIDDEN)
+            values (:1, :2, :3, :4, :5)
+            """, rname, pname, pobject, pvalue, "T" if hidden else "F"
+        )
+       
+    def _delete_from_db(self, rname, pname):
+        """
+        Remove a property.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pname: a C{str} containing the name of the property to get.
+        @return: a C{str} containing the property value.
+        """
+
+        self._db_execute("delete from PROPERTIES where RESOURCENAME = :1 and PROPERTYNAME = :2", rname, pname)
+    
+    def _delete_all_from_db(self, rname):
+        """
+        Remove a property.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pname: a C{str} containing the name of the property to get.
+        @return: a C{str} containing the property value.
+        """
+
+        self._db_execute("delete from PROPERTIES where RESOURCENAME = :1", rname)
+    
+    def _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return SQLPropertiesDatabase.dbType
+        
+    def _db_version(self):
+        """
+        @return: the schema version assigned to this index.
+        """
+        return SQLPropertiesDatabase.dbFormatVersion
+
+    def _db_init_data_tables(self, q):
+        """
+        Initialise the underlying database tables.
+        @param q:           a database cursor to use.
+        """
+
+        #
+        # PROPERTIES table
+        #
+        q.execute(
+            """
+            create table PROPERTIES (
+                RESOURCENAME   text,
+                PROPERTYNAME   text,
+                PROPERTYOBJECT text,
+                PROPERTYVALUE  text,
+                HIDDEN         text(1)
+            )
+            """
+        )
+        q.execute(
+            """
+            create index RESOURCE on PROPERTIES (RESOURCENAME)
+            """
+        )
+        q.execute(
+            """
+            create index RESOURCEandPROPERTY on PROPERTIES (RESOURCENAME, PROPERTYNAME)
+            """
+        )

Modified: CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/static.py	2008-06-17 01:39:59 UTC (rev 2567)
+++ CalendarServer/branches/users/cdaboo/sqlpropstore-2563/twistedcaldav/static.py	2008-06-17 03:54:13 UTC (rev 2568)
@@ -13,6 +13,7 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 ##
+from twistedcaldav.sqlprops import sqlPropertyStore
 
 """
 CalDAV-aware static resources.
@@ -87,7 +88,8 @@
 
     def deadProperties(self):
         if not hasattr(self, "_dead_properties"):
-            self._dead_properties = CachingXattrPropertyStore(self)
+            self._dead_properties = sqlPropertyStore(self)
+            #self._dead_properties = CachingXattrPropertyStore(self)
 
         return self._dead_properties
 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20080616/fa7efcda/attachment-0001.htm 


More information about the calendarserver-changes mailing list