[CalendarServer-changes] [1131] CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav

source_changes at macosforge.org source_changes at macosforge.org
Mon Feb 5 16:31:28 PST 2007


Revision: 1131
          http://trac.macosforge.org/projects/calendarserver/changeset/1131
Author:   cdaboo at apple.com
Date:     2007-02-05 16:31:27 -0800 (Mon, 05 Feb 2007)

Log Message:
-----------
Test implementation of an SQL dead property store.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/__init__.py
    CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/static.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py
    CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/__init__.py	2007-02-06 00:03:55 UTC (rev 1130)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/__init__.py	2007-02-06 00:31:27 UTC (rev 1131)
@@ -45,6 +45,7 @@
     "root",
     "schedule",
     "sql",
+    "sqlprops",
     "static",
 ]
 

Added: CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/sqlprops.py	2007-02-06 00:31:27 UTC (rev 1131)
@@ -0,0 +1,312 @@
+##
+# 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.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+from twistedcaldav.root import RootResource
+from twisted.python import log
+
+"""
+DAV Property store an sqlite database.
+
+This API is considered private to static.py and is therefore subject to
+change.
+"""
+
+__all__ = ["sqlPropertyStore"]
+
+import os
+import urllib
+
+from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError, StatusResponse
+from twisted.web2.dav import davxml
+
+from twistedcaldav.sql import AbstractSQLDatabase
+
+class sqlPropertyStore (object):
+    """
+
+    """
+ 
+    def _encode(clazz, name):
+        return urllib.quote("{%s}%s" % name, safe='{}:')
+
+    def _decode(clazz, name):
+        name = urllib.unquote(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:])
+
+    _encode = classmethod(_encode)
+    _decode = classmethod(_decode)
+
+    def __init__(self, resource):
+        self.resource = resource
+        if os.path.exists(os.path.dirname(resource.fp.path)):
+            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)
+        else:
+            self.index = 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.getPropertyValue(self.rname, self._encode(qname))
+        if not value:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No such property: {%s}%s" % qname
+            ))
+            
+        doc = davxml.WebDAVDocument.fromString(value)
+
+        return doc.root_element
+
+    def set(self, property):
+        """
+        Write property into index.
+        
+        @param property: C{Property} to write
+        """
+
+        if self.index:
+            self.index.setPropertyValue(self.rname, self._encode(property.qname()), property.toxml())
+
+    def delete(self, qname):
+        """
+        Delete proeprty from index.
+
+        DELETE from PROPERTIES where NAME=<<rname>> and PROPNAME=<<pname>>
+
+        @param qname:
+        """
+        
+        if self.index:
+            self.index.removeProperty(self.rname, self._encode(qname))
+
+    def deleteAll(self):
+        """
+        Delete proeprty from index.
+
+        DELETE from PROPERTIES where NAME=<<rname>> and PROPNAME=<<pname>>
+
+        @param qname:
+        """
+        
+        if self.index:
+            self.index.removeResource(self.rname)
+
+    def contains(self, qname):
+        if self.index:
+            value = self.index.getPropertyValue(self.rname, self._encode(qname))
+            return value != None
+        else:
+            return False
+
+    def list(self):
+        """
+        List all property names for this resource.
+        
+        SELECT PROPNAME from PROPERTIES where NAME=<<rname>>
+        
+        """
+
+        if self.index:
+            results = self.index.listProperties(self.rname)
+            result = [self._decode(name) for name in results]
+            return result
+        else:
+            return []
+
+class SQLPropertiesDatabase(AbstractSQLDatabase):
+    """
+    A database to maintain calendar user proxy group memberships.
+
+    SCHEMA:
+    
+    Properties Database:
+    
+    ROW: RESOURCENAME, PROPERTYNAME, PROPERTYVALUE
+    
+    """
+    
+    dbType = "SQLPROPERTIES"
+    dbFilename = ".db.sqlproperties"
+    dbFormatVersion = "1"
+
+    def __init__(self, path):
+        path = os.path.join(path, SQLPropertiesDatabase.dbFilename)
+        super(SQLPropertiesDatabase, self).__init__(path, SQLPropertiesDatabase.dbFormatVersion)
+
+    def setPropertyValue(self, rname, pname, pvalue):
+        """
+        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: a C{str} containing the property value to set.
+        """
+        
+        # Remove what is there, then add it back.
+        self._delete_from_db(rname, pname)
+        self._add_to_db(rname, pname, pvalue)
+        self._db_commit()
+
+    def getPropertyValue(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: a C{str} containing the property value.
+        """
+        
+        # Remove what is there, then add it back.
+        log.msg("getPropertyValue: %s \"%s\" \"%s\"" % (self.dbpath, rname, pname))
+        members = []
+        for row in self._db_execute("select PROPERTYVALUE from PROPERTIES where RESOURCENAME = :1 and PROPERTYNAME = :2", rname, pname):
+            members.append(row[0])
+        setlength =  len(members)
+        if setlength == 0:
+            return None
+        elif setlength == 1:
+            return members[0]
+        else:
+            raise ValueError("Multiple properties of the same name %s stored for resource %s" % (pname, rname,))
+        
+
+    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 get.
+        @return: a C{str} containing the property value.
+        """
+
+        self._delete_from_db(rname, pname)
+        self._db_commit()
+
+    def removeResource(self, rname):
+        """
+        Remove all properties for resource.
+    
+        @param rname: a C{str} containing the resource name.
+        @return: a C{str} containing the property value.
+        """
+
+        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(row[0])
+        return members
+
+    def _add_to_db(self, rname, pname, pvalue):
+        """
+        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: a C{str} containing the property value to set.
+        """
+        
+        self._db_execute(
+            """
+            insert into PROPERTIES (RESOURCENAME, PROPERTYNAME, PROPERTYVALUE)
+            values (:1, :2, :3)
+            """, rname, pname, pvalue
+        )
+       
+    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 _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 _db_type(self):
+        """
+        @return: the collection type assigned to this index.
+        """
+        return SQLPropertiesDatabase.dbType
+        
+    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,
+                PROPERTYVALUE  text
+            )
+            """
+        )

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/static.py	2007-02-06 00:03:55 UTC (rev 1130)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/static.py	2007-02-06 00:31:27 UTC (rev 1131)
@@ -61,6 +61,7 @@
 from twistedcaldav.notifications import NotificationsCollectionResource, NotificationResource
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
+from twistedcaldav.sqlprops import sqlPropertyStore
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource, DropBoxChildResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeTypeProvisioningResource
@@ -81,6 +82,14 @@
     # CalDAV
     ##
 
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            #if self.fp.path.find("user01") != -1:
+                self._dead_properties = sqlPropertyStore(self)
+            #else:
+            #    self._dead_properties = xattrPropertyStore(self)
+        return self._dead_properties
+
     def resourceType(self):
 	if self.isCalendarCollection():
 	    return davxml.ResourceType.calendar

Added: CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1126/twistedcaldav/test/test_sqlprops.py	2007-02-06 00:31:27 UTC (rev 1131)
@@ -0,0 +1,123 @@
+##
+# Copyright (c) 2005-2007 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.
+#
+# DRI: Wilfredo Sanchez, wsanchez at apple.com
+##
+from twistedcaldav import customxml
+from twistedcaldav import caldavxml
+from twisted.web2.dav import davxml
+
+from twistedcaldav.static import CalDAVFile
+
+import os
+
+from twistedcaldav.sqlprops import SQLPropertiesDatabase
+
+from twistedcaldav.sqlprops import sqlPropertyStore
+
+import twistedcaldav.test.util
+
+class SQLProps (twistedcaldav.test.util.TestCase):
+    """
+    SQL properties tests
+    """
+
+    props = (
+        davxml.DisplayName.fromString("My Name"),
+        davxml.ACL(
+            davxml.ACE(
+                davxml.Principal(davxml.Authenticated()),
+                davxml.Grant(davxml.Privilege(davxml.Read())),
+                davxml.Protected(),
+            ),
+        ),
+        caldavxml.CalendarDescription.fromString("My Calendar"),
+        customxml.TwistedScheduleAutoRespond(),
+    )
+    
+    def _setUpIndex(self):
+        self.collection_name, self.collection_uri = self.mkdtemp("sql")
+        rsrc = CalDAVFile(os.path.join(self.collection_name, "file.ics"))
+        return sqlPropertyStore(rsrc)
+        
+    def _setOnePropertyAndTest(self, prop):
+        index = self._setUpIndex()
+        index.set(prop)
+        self.assertTrue(index.contains(prop.qname()),
+                        msg="Could not find property %s." % prop)
+        self.assertTrue(index.get(prop.qname()) == prop,
+                        msg="Could not get property %s." % prop)
+        
+    def _setProperty(self, index, prop):
+        index.set(prop)
+        
+    def _testProperty(self, index, prop):
+        self.assertTrue(index.contains(prop.qname()),
+                        msg="Could not find property %s." % prop)
+        self.assertTrue(index.get(prop.qname()) == prop,
+                        msg="Could not get property %s." % prop)
+        
+    def test_db_init_directory(self):
+        self.collection_name, self.collection_uri = self.mkdtemp("sql")
+        rsrc = CalDAVFile(self.collection_name)
+        index = sqlPropertyStore(rsrc)
+        db = index.index._db()
+        self.assertTrue(os.path.exists(os.path.join(self.collection_name, SQLPropertiesDatabase.dbFilename)),
+                        msg="Could not initialize index via collection resource.")
+
+    def test_db_init_file(self):
+        index = self._setUpIndex()
+        db = index.index._db()
+        self.assertTrue(os.path.exists(os.path.join(self.collection_name, SQLPropertiesDatabase.dbFilename)),
+                        msg="Could not initialize index via file resource.")
+
+    def test_setoneproperty(self):
+        for prop in SQLProps.props:
+            self._setOnePropertyAndTest(prop)
+
+    def test_setmultipleproperties(self):
+        index = self._setUpIndex()
+        for prop in SQLProps.props:
+            self._setProperty(index, prop)
+        for prop in SQLProps.props:
+            self._testProperty(index, prop)
+        proplist = set(index.list())
+        expected_proplist = set([prop.qname() for prop in SQLProps.props])
+        self.assertTrue(proplist == expected_proplist,
+                        msg="Property lists do not match: %s != %s." % (proplist, expected_proplist))
+
+    def test_deleteproperties(self):
+        index = self._setUpIndex()
+        for prop in SQLProps.props:
+            self._setProperty(index, prop)
+        
+        remaining_props = [p for p in SQLProps.props]
+        for prop in SQLProps.props:
+            remaining_props.pop(0)
+            index.delete(prop.qname())
+            proplist = set(index.list())
+            expected_proplist = set([prop.qname() for prop in remaining_props])
+            self.assertTrue(proplist == expected_proplist,
+                            msg="Property lists do not match: %s != %s." % (proplist, expected_proplist))
+
+    def test_deleteallproperties(self):
+        index = self._setUpIndex()
+        for prop in SQLProps.props:
+            self._setProperty(index, prop)
+        
+        index.deleteAll()
+        for prop in SQLProps.props:
+            self.assertFalse(index.contains(prop.qname()),
+                            msg="Could not find property %s." % prop)

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070205/0e615f74/attachment.html


More information about the calendarserver-changes mailing list