[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