[CalendarServer-changes] [2569] CalendarServer/branches/users/cdaboo/sqlprops-2557

source_changes at macosforge.org source_changes at macosforge.org
Tue Jun 17 07:27:51 PDT 2008


Revision: 2569
          http://trac.macosforge.org/projects/calendarserver/changeset/2569
Author:   cdaboo at apple.com
Date:     2008-06-17 07:27:41 -0700 (Tue, 17 Jun 2008)

Log Message:
-----------
Merged forward. Note this is not a viable solution as live properties are not handled properly.
An alternative solution is being worked on.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
    CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
    CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch
    CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
    CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/__init__.py
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/extensions.py
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/root.py
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/sql.py
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/tap.py

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

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.copymove.patch	2008-06-17 14:27:41 UTC (rev 2569)
@@ -53,16 +53,27 @@
          yield destparent
          destparent = destparent.getResult()
  
-@@ -144,7 +155,19 @@
+@@ -144,9 +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
++        properties = self.deadProperties().getAll()
++        destination.deadProperties().setSeveral([p for p in properties.itervalues()])
++        self.deadProperties().deleteAll()
++        
++        yield result
 +    else:
 +        x = waitForDeferred(put_common.storeResource(request,
 +                                                     source=self,
@@ -71,6 +82,8 @@
 +                                                     destination_uri=destination_uri,
 +                                                     deletesource=True,
 +                                                     depth=depth))
-     yield x
-     yield x.getResult()
++        yield x
++        yield x.getResult()
  
+ http_MOVE = deferredGenerator(http_MOVE)
+ 

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2008-06-17 14:27:41 UTC (rev 2569)
@@ -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/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	2008-06-17 14:27:41 UTC (rev 2569)
@@ -2,8 +2,231 @@
 ===================================================================
 --- twisted/web2/dav/method/propfind.py	(revision 19773)
 +++ twisted/web2/dav/method/propfind.py	(working copy)
-@@ -200,7 +200,7 @@
+@@ -22,6 +22,7 @@
+ #
+ # DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
++from twisted.web2.dav.util import joinURL
  
+ """
+ WebDAV PROPFIND method
+@@ -30,13 +31,12 @@
+ __all__ = ["http_PROPFIND"]
+ 
+ from twisted.python import log
+-from twisted.python.failure import Failure
+ from twisted.internet.defer import deferredGenerator, waitForDeferred
+ from twisted.web2.http import HTTPError
+ from twisted.web2 import responsecode
+ from twisted.web2.http import StatusResponse
+ from twisted.web2.dav import davxml
+-from twisted.web2.dav.http import MultiStatusResponse, statusForFailure
++from twisted.web2.dav.http import MultiStatusResponse
+ from twisted.web2.dav.util import normalizeURL, davXMLFromStream
+ 
+ def http_PROPFIND(self, request):
+@@ -114,79 +114,136 @@
+     yield filtered_aces
+     filtered_aces = filtered_aces.getResult()
+ 
+-    resources = [(self, my_url)]
++    resources = [my_url.rstrip("/")]
+ 
+-    d = self.findChildren(depth, request, lambda x, y: resources.append((x, y)), (davxml.Read(),), inherited_aces=filtered_aces)
+-    x = waitForDeferred(d)
+-    yield x
+-    x.getResult()
++    if depth == "1":
++        d = self.findChildNames(request, lambda x: resources.append(joinURL(request.uri, x)), privileges=(davxml.Read(),), inherited_aces=filtered_aces)
++        x = waitForDeferred(d)
++        yield x
++        x.getResult()
++    # TODO: depth=infinity
+ 
+-    for resource, uri in resources:
+-        if search_properties is "names":
+-            try:
+-                resource_properties = waitForDeferred(resource.listProperties(request))
+-                yield resource_properties
+-                resource_properties = resource_properties.getResult()
+-            except:
+-                log.err("Unable to get properties for resource %r" % (resource,))
+-                raise
+-
++    if search_properties is "names":
++        results = {}
++        results[request.uri] = self.deadProperties().list()
++        
++        if depth == "1":
++            allowed_uris = set(resources)
++            child_results = self.deadProperties().listAll()
++            for key, value in child_results.iteritems():
++                uri = joinURL(request.uri, key)
++                if uri in allowed_uris:
++                    results[uri] = value
++ 
++        uris = results.keys()
++        uris.sort()
++        for uri in uris:
+             properties_by_status = {
+-                responsecode.OK: [propertyName(p) for p in resource_properties]
++                responsecode.OK: [propertyName(p) for p in results[uri]]
+             }
+-        else:
++            propstats = []
++    
++            for status in properties_by_status:
++                properties = properties_by_status[status]
++                if not properties: continue
++    
++                xml_status    = davxml.Status.fromResponseCode(status)
++                xml_container = davxml.PropertyContainer(*properties)
++                xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
++    
++                propstats.append(xml_propstat)
++    
++            xml_resource = davxml.HRef(uri)
++            if propstats:
++                xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats)
++            else:
++                xml_response = davxml.StatusResponse(xml_resource, davxml.Status.fromResponseCode(responsecode.OK))
++    
++            xml_responses.append(xml_response)
++
++    elif search_properties is "all":
++        results = {}
++        results[request.uri] = self.deadProperties().getAll(nohidden=True)
++        
++        if depth == "1":
++            allowed_uris = set(resources)
++            child_results = self.deadProperties().getAllResources(nohidden=True)
++            for key, value in child_results.iteritems():
++                uri = joinURL(request.uri, key)
++                if uri in allowed_uris:
++                    results[uri] = value
++
++        uris = results.keys()
++        uris.sort()
++        for uri in uris:
+             properties_by_status = {
+-                responsecode.OK        : [],
+-                responsecode.NOT_FOUND : [],
++                responsecode.OK: [p for p in results[uri].itervalues()]
+             }
+-
+-            if search_properties is "all":
+-                properties_to_enumerate = waitForDeferred(resource.listAllprop(request))
+-                yield properties_to_enumerate
+-                properties_to_enumerate = properties_to_enumerate.getResult()
++            propstats = []
++    
++            for status in properties_by_status:
++                properties = properties_by_status[status]
++                if not properties: continue
++    
++                xml_status    = davxml.Status.fromResponseCode(status)
++                xml_container = davxml.PropertyContainer(*properties)
++                xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
++    
++                propstats.append(xml_propstat)
++    
++            xml_resource = davxml.HRef(uri)
++            if propstats:
++                xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats)
+             else:
+-                properties_to_enumerate = search_properties
++                xml_response = davxml.StatusResponse(xml_resource, davxml.Status.fromResponseCode(responsecode.OK))
++    
++            xml_responses.append(xml_response)
++    
++    else:
++        results = {}
++        results[request.uri] = self.deadProperties().getSeveral(search_properties)
++        
++        if depth == "1":
++            allowed_uris = set(resources)
++            child_results = self.deadProperties().getSeveralResources(search_properties)
++            for key, value in child_results.iteritems():
++                uri = joinURL(request.uri, key)
++                if uri in allowed_uris:
++                    results[uri] = value
+ 
+-            for property in properties_to_enumerate:
+-                has = waitForDeferred(resource.hasProperty(property, request))
+-                yield has
+-                has = has.getResult()
+-                if has:
+-                    try:
+-                        resource_property = waitForDeferred(resource.readProperty(property, request))
+-                        yield resource_property
+-                        resource_property = resource_property.getResult()
+-                    except:
+-                        f = Failure()
+-
+-                        log.err("Error reading property %r for resource %s: %s" % (property, uri, f.value))
+-
+-                        status = statusForFailure(f, "getting property: %s" % (property,))
+-                        if status not in properties_by_status:
+-                            properties_by_status[status] = []
+-                        properties_by_status[status].append(propertyName(property))
+-                    else:
+-                        properties_by_status[responsecode.OK].append(resource_property)
++        uris = results.keys()
++        uris.sort()
++        for uri in uris:
++            properties_by_status = {
++                 responsecode.OK        : [],
++                 responsecode.NOT_FOUND : [],
++             }
++            for prop in search_properties:
++                if results[uri].has_key(prop):
++                    properties_by_status[responsecode.OK].append(results[uri][prop])
+                 else:
+-                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(property))
++                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(prop))
++ 
++            propstats = []
++    
++            for status in properties_by_status:
++                properties = properties_by_status[status]
++                if not properties: continue
++    
++                xml_status    = davxml.Status.fromResponseCode(status)
++                xml_container = davxml.PropertyContainer(*properties)
++                xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
++    
++                propstats.append(xml_propstat)
++    
++            xml_resource = davxml.HRef(uri)
++            if propstats:
++                xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats)
++            else:
++                xml_response = davxml.StatusResponse(xml_resource, davxml.Status.fromResponseCode(responsecode.OK))
++    
++            xml_responses.append(xml_response)
+ 
+-        propstats = []
+-
+-        for status in properties_by_status:
+-            properties = properties_by_status[status]
+-            if not properties: continue
+-
+-            xml_status    = davxml.Status.fromResponseCode(status)
+-            xml_container = davxml.PropertyContainer(*properties)
+-            xml_propstat  = davxml.PropertyStatus(xml_container, xml_status)
+-
+-            propstats.append(xml_propstat)
+-
+-        xml_resource = davxml.HRef(uri)
+-        xml_response = davxml.PropertyStatusResponse(xml_resource, *propstats)
+-
+-        xml_responses.append(xml_response)
+-
+     #
+     # Return response
+     #
+@@ -200,7 +257,7 @@
+ 
  def propertyName(name):
      property_namespace, property_name = name
 -    class PropertyName (davxml.WebDAVEmptyElement):

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.method.put_common.patch	2008-06-17 14:27:41 UTC (rev 2569)
@@ -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,274 @@
 +##
 +# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
 +#
@@ -211,6 +211,11 @@
 +        yield response
 +        response = response.getResult()
 +
++        # Copy dead properties first, before adding overridden values
++        if source is not None:
++            properties = source.deadProperties().getAll()
++            destination.deadProperties().setSeveral([p for p in properties.itervalues()])
++
 +        # Update the MD5 value on the resource
 +        if source is not None:
 +            # Copy MD5 value from source to destination
@@ -256,6 +261,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/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.resource.patch	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/lib-patches/Twisted/twisted.web2.dav.resource.patch	2008-06-17 14:27:41 UTC (rev 2569)
@@ -91,7 +91,7 @@
  
                  def ifAllowed(privileges, callback):
                      def onError(failure):
-@@ -286,7 +307,36 @@
+@@ -286,14 +307,39 @@
                          d.addCallback(gotACL)
                          return d
                      return ifAllowed((davxml.ReadACL(),), callback)
@@ -127,8 +127,16 @@
 +
              elif namespace == twisted_dav_namespace:
                  if name == "resource-class":
-                     class ResourceClass (davxml.WebDAVTextElement):
-@@ -363,15 +413,28 @@
+-                    class ResourceClass (davxml.WebDAVTextElement):
+-                        namespace = twisted_dav_namespace
+-                        name = "resource-class"
+-                        hidden = False
+-                    return ResourceClass(self.__class__.__name__)
++                    return TwistedResourceClass(self.__class__.__name__)
+ 
+             elif namespace == twisted_private_namespace:
+                 raise HTTPError(StatusResponse(
+@@ -363,15 +409,28 @@
          """
          See L{IDAVResource.listProperties}.
          """
@@ -161,7 +169,7 @@
      def listAllprop(self, request):
          """
          Some DAV properties should not be returned to a C{DAV:allprop} query.
-@@ -465,8 +528,22 @@
+@@ -465,8 +524,22 @@
              return super(DAVPropertyMixIn, self).displayName()
  
  class DAVResource (DAVPropertyMixIn, StaticRenderMixin):
@@ -184,7 +192,7 @@
      ##
      # DAV
      ##
-@@ -570,19 +647,21 @@
+@@ -570,19 +643,21 @@
          See L{IDAVResource.authorize}.
          """
          def onError(failure):
@@ -211,7 +219,7 @@
                      response = UnauthorizedResponse(request.credentialFactories,
                                                      request.remoteAddr)
                  else:
-@@ -593,7 +672,7 @@
+@@ -593,7 +668,7 @@
                  # class is supposed to be a FORBIDDEN status code and
                  # "Authorization will not help" according to RFC2616
                  #
@@ -220,7 +228,7 @@
  
              d = self.checkPrivileges(request, privileges, recurse)
              d.addErrback(onErrors)
-@@ -606,16 +685,21 @@
+@@ -606,16 +681,21 @@
  
      def authenticate(self, request):
          def loginSuccess(result):
@@ -246,7 +254,7 @@
  
          authHeader = request.headers.getHeader('authorization')
  
-@@ -631,9 +715,10 @@
+@@ -631,9 +711,10 @@
  
                  # Try to match principals in each principal collection on the resource
                  def gotDetails(details):
@@ -260,7 +268,7 @@
  
                  def login(pcreds):
                      d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -641,13 +726,15 @@
+@@ -641,13 +722,15 @@
  
                      return d
  
@@ -280,7 +288,7 @@
  
      ##
      # ACL
-@@ -656,49 +743,23 @@
+@@ -656,49 +739,23 @@
      def currentPrincipal(self, request):
          """
          @param request: the request being processed.
@@ -339,7 +347,7 @@
          """
          @return: the L{davxml.ACL} element containing the default access control
              list for this resource.
-@@ -710,6 +771,17 @@
+@@ -710,6 +767,17 @@
          #
          return readonlyACL
  
@@ -357,7 +365,7 @@
      def setAccessControlList(self, acl):
          """
          See L{IDAVResource.setAccessControlList}.
-@@ -748,13 +820,16 @@
+@@ -748,13 +816,16 @@
          # 10. Verify that new acl is not in conflict with itself
          # 11. Update acl on the resource
  
@@ -375,7 +383,7 @@
  
          # Need to get list of supported privileges
          supported = []
-@@ -1038,9 +1113,9 @@
+@@ -1038,9 +1109,9 @@
  
              if myURL == "/":
                  # If we get to the root without any ACLs, then use the default.
@@ -387,7 +395,7 @@
  
          # Dynamically update privileges for those ace's that are inherited.
          if inheritance:
-@@ -1076,7 +1151,7 @@
+@@ -1076,7 +1147,7 @@
                                  # Adjust ACE for inherit on this resource
                                  children = list(ace.children)
                                  children.remove(TwistedACLInheritable())
@@ -396,7 +404,7 @@
                                  aces.append(davxml.ACE(*children))
              else:
                  aces.extend(inherited_aces)
-@@ -1105,8 +1180,7 @@
+@@ -1105,8 +1176,7 @@
          the child resource loop and supply those to the checkPrivileges on each child.
  
          @param request: the L{IRequest} for the request in progress.
@@ -406,7 +414,7 @@
          """
          
          # Get the parent ACLs with inheritance and preserve the <inheritable> element.
-@@ -1128,21 +1202,9 @@
+@@ -1128,21 +1198,9 @@
                  # Adjust ACE for inherit on this resource
                  children = list(ace.children)
                  children.remove(TwistedACLInheritable())
@@ -430,7 +438,7 @@
  
      inheritedACEsforChildren = deferredGenerator(inheritedACEsforChildren)
  
-@@ -1152,49 +1214,69 @@
+@@ -1152,49 +1210,69 @@
  
          This implementation returns an empty set.
          """
@@ -528,7 +536,7 @@
      def samePrincipal(self, principal1, principal2):
          """
          Check whether the two prinicpals are exactly the same in terms of
-@@ -1219,7 +1301,6 @@
+@@ -1219,7 +1297,6 @@
              return False
                  
      def matchPrincipal(self, principal1, principal2, request):
@@ -536,7 +544,7 @@
          """
          Check whether the principal1 is a principal in the set defined by
          principal2.
-@@ -1244,6 +1325,9 @@
+@@ -1244,6 +1321,9 @@
              if isinstance(principal1, davxml.Unauthenticated):
                  yield False
                  return
@@ -546,7 +554,7 @@
              else:
                  yield True
                  return
-@@ -1271,7 +1355,6 @@
+@@ -1271,7 +1351,6 @@
  
          assert principal2 is not None, "principal2 is None"
  
@@ -554,7 +562,7 @@
          # Compare two HRefs and do group membership test as well
          if principal1 == principal2:
              yield True
-@@ -1289,6 +1372,7 @@
+@@ -1289,6 +1368,7 @@
  
      matchPrincipal = deferredGenerator(matchPrincipal)
  
@@ -562,7 +570,7 @@
      def principalIsGroupMember(self, principal1, principal2, request):
          """
          Check whether one principal is a group member of another.
-@@ -1299,18 +1383,21 @@
+@@ -1299,18 +1379,21 @@
          @return: L{Deferred} with result C{True} if principal1 is a member of principal2, C{False} otherwise
          """
          
@@ -595,7 +603,7 @@
          
      def validPrincipal(self, ace_principal, request):
          """
-@@ -1351,11 +1438,16 @@
+@@ -1351,11 +1434,16 @@
          @return C{True} if C{href_principal} is valid, C{False} otherwise.
  
          This implementation tests for a href element that corresponds to
@@ -615,7 +623,7 @@
          return d
  
      def resolvePrincipal(self, principal, request):
-@@ -1432,7 +1524,7 @@
+@@ -1432,7 +1520,7 @@
                  log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
                  yield None
                  return
@@ -624,7 +632,7 @@
  
          if isinstance(principal, davxml.HRef):
              yield principal
-@@ -1517,6 +1609,270 @@
+@@ -1517,6 +1605,270 @@
          return None
  
      ##
@@ -895,7 +903,7 @@
      # HTTP
      ##
  
-@@ -1525,10 +1881,6 @@
+@@ -1525,10 +1877,6 @@
          #litmus = request.headers.getRawHeaders("x-litmus")
          #if litmus: log.msg("*** Litmus test: %s ***" % (litmus,))
  
@@ -906,7 +914,7 @@
          #
          # If this is a collection and the URI doesn't end in "/", redirect.
          #
-@@ -1567,7 +1919,7 @@
+@@ -1567,7 +1915,7 @@
      def findChildren(self, depth, request, callback, privileges=None, inherited_aces=None):
          return succeed(None)
  
@@ -915,7 +923,7 @@
      """
      Resource representing a WebDAV principal.  (RFC 3744, section 2)
      """
-@@ -1577,7 +1929,7 @@
+@@ -1577,7 +1925,7 @@
      # WebDAV
      ##
  
@@ -924,7 +932,7 @@
          (dav_namespace, "alternate-URI-set"),
          (dav_namespace, "principal-URL"    ),
          (dav_namespace, "group-member-set" ),
-@@ -1585,14 +1937,11 @@
+@@ -1585,14 +1933,11 @@
      )
  
      def davComplianceClasses(self):
@@ -940,7 +948,7 @@
      def readProperty(self, property, request):
          def defer():
              if type(property) is tuple:
-@@ -1610,10 +1959,20 @@
+@@ -1610,10 +1955,20 @@
                      return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
  
                  if name == "group-member-set":
@@ -963,7 +971,7 @@
  
                  if name == "resourcetype":
                      if self.isCollection():
-@@ -1655,7 +2014,7 @@
+@@ -1655,7 +2010,7 @@
          principals.  Subclasses should override this method to provide member
          URLs for this resource if appropriate.
          """
@@ -972,7 +980,7 @@
  
      def groupMemberships(self):
          """
-@@ -1666,6 +2025,7 @@
+@@ -1666,6 +2021,7 @@
          """
          unimplemented(self)
  
@@ -980,7 +988,7 @@
      def principalMatch(self, href):
          """
          Check whether the supplied principal matches this principal or is a
-@@ -1675,10 +2035,33 @@
+@@ -1675,10 +2031,33 @@
          """
          uri = str(href)
          if self.principalURL() == uri:
@@ -1016,7 +1024,7 @@
  class AccessDeniedError(Exception):
      def __init__(self, errors):
          """ 
-@@ -1718,6 +2101,37 @@
+@@ -1718,6 +2097,48 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  
@@ -1051,6 +1059,17 @@
 +
 +davxml.registerElement(TwistedQuotaUsedProperty)
 +
++"""
++Indicates the Python class used, a type derived from DAVResource, that represents the WebDAV
++resource on which it is defined as a property.
++"""
++class TwistedResourceClass (davxml.WebDAVTextElement):
++    namespace = twisted_dav_namespace
++    name = "resource-class"
++    hidden = False
++
++davxml.registerElement(TwistedResourceClass)
++
  allACL = davxml.ACL(
      davxml.ACE(
          davxml.Principal(davxml.All()),

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/__init__.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/__init__.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/__init__.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -41,12 +41,14 @@
     "itip",
     "log",
     "memcache",
+    "memcachepool",
     "memcacher",
     "principalindex",
     "resource",
     "root",
     "schedule",
     "sql",
+    "sqlprops",
     "static",
 ]
 

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/extensions.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/extensions.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -14,6 +14,7 @@
 # limitations under the License.
 ##
 
+
 """
 Extensions to web2.dav
 """
@@ -31,28 +32,37 @@
 import cPickle as pickle
 import urllib
 import cgi
+import os
 import time
 
+from twisted.internet import reactor
+from twisted.internet.defer import Deferred
+from twisted.internet.defer import maybeDeferred
 from twisted.internet.defer import succeed, deferredGenerator, waitForDeferred
-from twisted.internet.defer import maybeDeferred
 from twisted.web2 import responsecode
-from twisted.web2.http import HTTPError, Response, RedirectResponse
-from twisted.web2.http_headers import MimeType
-from twisted.web2.stream import FileStream
-from twisted.web2.static import MetaDataMixin
 from twisted.web2.dav import davxml
 from twisted.web2.dav.davxml import dav_namespace
 from twisted.web2.dav.http import StatusResponse
+from twisted.web2.dav.resource import AccessDeniedError
+from twisted.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
+from twisted.web2.dav.resource import DAVResource as SuperDAVResource
+from twisted.web2.dav.resource import TwistedACLInheritable
+from twisted.web2.dav.resource import TwistedResourceClass
 from twisted.web2.dav.static import DAVFile as SuperDAVFile
-from twisted.web2.dav.resource import DAVResource as SuperDAVResource
-from twisted.web2.dav.resource import DAVPrincipalResource as SuperDAVPrincipalResource
 from twisted.web2.dav.util import joinURL
+from twisted.web2.dav.util import parentForURL
 from twisted.web2.dav.xattrprops import xattrPropertyStore
+from twisted.web2.http import HTTPError, Response, RedirectResponse
+from twisted.web2.http_headers import MimeType
+from twisted.web2.http_headers import generateContentType
+from twisted.web2.static import MetaDataMixin
+from twisted.web2.stream import FileStream
 
 from twistedcaldav.log import Logger, LoggingMixIn
 from twistedcaldav.util import submodule
 from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.directory import DirectoryService
+from twistedcaldav.sqlprops import sqlPropertyStore
 
 log = Logger()
 
@@ -202,8 +212,462 @@
 
     return fun
 
+class DAVPropertyHandlerMixin(object):
 
-class DAVResource (SudoSACLMixin, SuperDAVResource, LoggingMixIn):
+    def create(self):
+        self.updateProperties()
+
+    def update(self):
+        self.updateProperties()
+
+    def updateProperties(self):
+        """
+        Push "live" properties into the dead property store".
+        """
+        
+        props_to_set = []
+        # resourcetype
+        if self.isCollection():
+            props_to_set.append(davxml.ResourceType.collection)
+        else:
+            props_to_set.append(davxml.ResourceType.empty)
+            
+        # getetag
+        etag = self.etag()
+        if etag is not None:
+            props_to_set.append(davxml.GETETag(etag.generate()))
+
+        # getcontenttype
+        mimeType = self.contentType()
+        if mimeType is not None:
+            mimeType.params = None # WebDAV getcontenttype property does not include parameters
+            props_to_set.append(davxml.GETContentType(generateContentType(mimeType)))
+
+        # getcontentlength
+        length = self.contentLength()
+        if length is None:
+            # TODO: really we should "render" the resource and 
+            # determine its size from that but for now we just 
+            # return an empty element.
+            props_to_set.append(davxml.GETContentLength(""))
+        else:
+            props_to_set.append(davxml.GETContentLength(str(length)))
+
+        # getlastmodified
+        lastModified = self.lastModified()
+        if lastModified is not None:
+            props_to_set.append(davxml.GETLastModified.fromDate(lastModified))
+
+        # creationdate
+        creationDate = self.creationDate()
+        if creationDate is not None:
+            props_to_set.append(davxml.CreationDate.fromDate(creationDate))
+
+        # displayname
+        displayName = self.displayName()
+        if displayName is not None:
+            props_to_set.append(davxml.DisplayName(displayName))
+
+        # acl
+        props_to_set.append(self.defaultAccessControlList())
+        #from twistedcaldav.static import calendarPrivilegeSet
+        #props_to_set.append(calendarPrivilegeSet)
+        
+        # resource class
+        props_to_set.append(TwistedResourceClass(self.__class__.__name__))
+
+        # Now write out all properties in one go
+        self.deadProperties().setSeveral(props_to_set)
+
+    def isClassProperty(self, property):
+        if isinstance(property, tuple):
+            qname = property
+        else:
+            qname = property.qname()
+            
+        return qname in ((dav_namespace, "supported-privilege-set"),)
+
+    def computeClassProperty(self, classname, property):
+        from twistedcaldav.static import calendarPrivilegeSet
+
+        if isinstance(property, tuple):
+            namespace, name = property
+        else:
+            namespace, name = property.qname()
+        
+        if namespace == dav_namespace:
+            if name == "supported-privilege-set":
+                return calendarPrivilegeSet
+
+class DAVFastACLMixin(object):
+
+    def findChildNames(self, request, callbackAllowed, callbackForbidden=None, privileges=None, inherited_aces=None):
+
+        # Get aces for children
+        acls = self.deadProperties().getSeveralResources((
+            davxml.ACL.qname(),
+            TwistedResourceClass.qname(),
+            davxml.GETETag.qname(),
+        ))
+        aclmap = {}
+        for name, props in acls.iteritems():
+            acl = props.get(davxml.ACL.qname(), None)
+            privyset = self.computeClassProperty(props.get(TwistedResourceClass.qname(), None), (dav_namespace, "supported-privilege-set"))
+            aclmap.setdefault((acl, privyset), []).append(name)
+            
+        # Now determine whether each ace satisfies privileges
+        for (acl, privyset), names in aclmap.iteritems():
+            checked = waitForDeferred(self.checkACLPrivilege(request, acl, privyset, privileges, inherited_aces))
+            yield checked
+            checked = checked.getResult()
+            if checked:
+                for name in names:
+                    callbackAllowed(name)
+            elif callbackForbidden:
+                for name in names:
+                    callbackForbidden(name)
+
+        yield None
+
+    findChildNames = deferredGenerator(findChildNames)
+
+    def checkACLPrivilege(self, request, acl, privyset, privileges, inherited_aces):
+        
+        principal = self.currentPrincipal(request)
+
+        # Other principals types don't make sense as actors.
+        assert (
+            principal.children[0].name in ("unauthenticated", "href"),
+            "Principal is not an actor: %r" % (principal,)
+        )
+
+        acl = self.fullAccessControlList(acl, inherited_aces)
+
+        pending = list(privileges)
+        denied = []
+
+        for ace in acl.children:
+            for privilege in tuple(pending):
+                if not self.matchPrivilege(davxml.Privilege(privilege), ace.privileges, privyset):
+                    continue
+
+                match = waitForDeferred(self.matchPrincipal(principal, ace.principal, request))
+                yield match
+                match = match.getResult()
+
+                if match:
+                    if ace.invert:
+                        continue
+                else:
+                    if not ace.invert:
+                        continue
+
+                pending.remove(privilege)
+
+                if not ace.allow:
+                    denied.append(privilege)
+
+        yield len(denied) + len(pending) == 0
+
+    checkACLPrivilege = deferredGenerator(checkACLPrivilege)
+
+    def fullAccessControlList(self, acl, inherited_aces):
+        """
+        See L{IDAVResource.accessControlList}.
+
+        This implementation looks up the ACL in the private property
+        C{(L{twisted_private_namespace}, "acl")}.
+        If no ACL has been stored for this resource, it returns the value
+        returned by C{defaultAccessControlList}.
+        If access is disabled it will return C{None}.
+        """
+        #
+        # Inheritance is problematic. Here is what we do:
+        #
+        # 1. A private element <Twisted:inheritable> is defined for use inside
+        #    of a <DAV:ace>. This private element is removed when the ACE is
+        #    exposed via WebDAV.
+        #
+        # 2. When checking ACLs with inheritance resolution, the server must
+        #    examine all parent resources of the current one looking for any
+        #    <Twisted:inheritable> elements.
+        #
+        # If those are defined, the relevant ace is applied to the ACL on the
+        # current resource.
+        #
+
+        # Dynamically update privileges for those ace's that are inherited.
+        if acl:
+            aces = list(acl.children)
+        else:
+            aces = []
+
+        aces.extend(inherited_aces)
+
+        acl = davxml.ACL(*aces)
+
+        return acl
+    
+    def findChildrenBatch(self, depth, request, callback, privileges=None, inherited_aces=None):
+        """
+        See L{IDAVResource.findChildren}.
+
+        This implementation works for C{depth} values of C{"0"}, C{"1"}, 
+        and C{"infinity"}.  As long as C{self.listChildren} is implemented
+        """
+        assert depth in ("0", "1", "infinity"), "Invalid depth: %s" % (depth,)
+
+        if depth == "0" or not self.isCollection():
+            return succeed(None)
+
+        # Get aces for children
+        aclprops = self.deadProperties().getSeveralResources((
+            davxml.ACL.qname(),
+        ))
+        acls = {}
+        for name, props in acls.iteritems():
+            acls[name] = props.get(davxml.ACL.qname(), None)
+            
+        completionDeferred = Deferred()
+        basepath = request.urlForResource(self)
+        children = list(self.listChildren())
+
+        def checkPrivilegesError(failure):
+            failure.trap(AccessDeniedError)
+            reactor.callLater(0, getChild)
+
+        def checkPrivileges(child):
+            if child is None:
+                return None
+
+            if privileges is None:
+                return child
+   
+            cname = os.path.basename(request.urlForResource(self))
+            d = child.checkPrivilegesBatch(request, privileges, inherited_aces=inherited_aces, acl=acls.get(cname, None))
+            d.addCallback(lambda _: child)
+            return d
+
+        def gotChild(child, childpath):
+            if child is None:
+                callback(None, childpath + "/")
+            else:
+                if child.isCollection():
+                    callback(child, childpath + "/")
+                    if depth == "infinity":
+                        d = child.findChildren(depth, request, callback, privileges)
+                        d.addCallback(lambda x: reactor.callLater(0, getChild))
+                        return d
+                else:
+                    callback(child, childpath)
+
+            reactor.callLater(0, getChild)
+
+        def getChild():
+            try:
+                childname = children.pop()
+            except IndexError:
+                completionDeferred.callback(None)
+            else:
+                childpath = joinURL(basepath, childname)
+                d = request.locateChildResource(self, childname)
+                d.addCallback(checkPrivileges)
+                d.addCallbacks(gotChild, checkPrivilegesError, (childpath,))
+                d.addErrback(completionDeferred.errback)
+
+        getChild()
+
+        return completionDeferred
+
+    def checkPrivilegesBatch(self, request, privileges, recurse=False, principal=None, inherited_aces=None, acl=None):
+        """
+        Check whether the given principal has the given privileges.
+        (RFC 3744, section 5.5)
+        @param request: the request being processed.
+        @param privileges: an iterable of L{davxml.WebDAVElement} elements
+            denoting access control privileges.
+        @param recurse: C{True} if a recursive check on all child
+            resources of this resource should be performed as well,
+            C{False} otherwise.
+        @param principal: the L{davxml.Principal} to check privileges
+            for.  If C{None}, it is deduced from C{request} by calling
+            L{currentPrincipal}.
+        @param inherited_aces: a list of L{davxml.ACE}s corresponding to the precomputed
+            inheritable aces from the parent resource hierarchy.
+        @return: a L{Deferred} that callbacks with C{None} or errbacks with an
+            L{AccessDeniedError}
+        """
+        if principal is None:
+            principal = self.currentPrincipal(request)
+
+        supportedPrivs = waitForDeferred(self.supportedPrivileges(request))
+        yield supportedPrivs
+        supportedPrivs = supportedPrivs.getResult()
+
+        # Other principals types don't make sense as actors.
+        assert (
+            principal.children[0].name in ("unauthenticated", "href"),
+            "Principal is not an actor: %r" % (principal,)
+        )
+
+        errors = []
+
+        resources = [(self, None)]
+
+        if recurse:
+            x = self.findChildren("infinity", request, lambda x, y: resources.append((x,y)))
+            x = waitForDeferred(x)
+            yield x
+            x.getResult()
+
+        for resource, uri in resources:
+            acl = waitForDeferred(resource.accessControlListBatch(request, inherited_aces=inherited_aces, acl=acl))
+            yield acl
+            acl = acl.getResult()
+
+            # Check for disabled
+            if acl is None:
+                errors.append((uri, list(privileges)))
+                continue
+
+            pending = list(privileges)
+            denied = []
+
+            for ace in acl.children:
+                for privilege in tuple(pending):
+                    if not self.matchPrivilege(davxml.Privilege(privilege), ace.privileges, supportedPrivs):
+                        continue
+
+                    match = waitForDeferred(self.matchPrincipal(principal, ace.principal, request))
+                    yield match
+                    match = match.getResult()
+
+                    if match:
+                        if ace.invert:
+                            continue
+                    else:
+                        if not ace.invert:
+                            continue
+
+                    pending.remove(privilege)
+
+                    if not ace.allow:
+                        denied.append(privilege)
+
+            denied += pending # If no matching ACE, then denied
+
+            if denied: 
+                errors.append((uri, denied))
+
+        if errors:
+            raise AccessDeniedError(errors,)
+        
+        yield None
+
+    checkPrivilegesBatch = deferredGenerator(checkPrivilegesBatch)
+
+    def accessControlListBatch(self, request, inheritance=True, expanding=False, inherited_aces=None, acl=None):
+        """
+        See L{IDAVResource.accessControlList}.
+
+        This implementation looks up the ACL in the private property
+        C{(L{twisted_private_namespace}, "acl")}.
+        If no ACL has been stored for this resource, it returns the value
+        returned by C{defaultAccessControlList}.
+        If access is disabled it will return C{None}.
+        """
+        #
+        # Inheritance is problematic. Here is what we do:
+        #
+        # 1. A private element <Twisted:inheritable> is defined for use inside
+        #    of a <DAV:ace>. This private element is removed when the ACE is
+        #    exposed via WebDAV.
+        #
+        # 2. When checking ACLs with inheritance resolution, the server must
+        #    examine all parent resources of the current one looking for any
+        #    <Twisted:inheritable> elements.
+        #
+        # If those are defined, the relevant ace is applied to the ACL on the
+        # current resource.
+        #
+        myURL = None
+
+        def getMyURL():
+            url = request.urlForResource(self)
+
+            assert url is not None, "urlForResource(self) returned None for resource %s" % (self,)
+
+            return url
+
+        if acl is None:
+            # Produce a sensible default for an empty ACL.
+            if myURL is None:
+                myURL = getMyURL()
+
+            if myURL == "/":
+                # If we get to the root without any ACLs, then use the default.
+                acl = self.defaultRootAccessControlList()
+            else:
+                acl = self.defaultAccessControlList()
+
+        # Dynamically update privileges for those ace's that are inherited.
+        if inheritance:
+            aces = list(acl.children)
+
+            if myURL is None:
+                myURL = getMyURL()
+
+            if inherited_aces is None:
+                if myURL != "/":
+                    parentURL = parentForURL(myURL)
+    
+                    parent = waitForDeferred(request.locateResource(parentURL))
+                    yield parent
+                    parent = parent.getResult()
+    
+                    if parent:
+                        parent_acl = waitForDeferred(
+                            parent.accessControlList(request, inheritance=True, expanding=True)
+                        )
+                        yield parent_acl
+                        parent_acl = parent_acl.getResult()
+    
+                        # Check disabled
+                        if parent_acl is None:
+                            yield None
+                            return
+    
+                        for ace in parent_acl.children:
+                            if ace.inherited:
+                                aces.append(ace)
+                            elif TwistedACLInheritable() in ace.children:
+                                # Adjust ACE for inherit on this resource
+                                children = list(ace.children)
+                                children.remove(TwistedACLInheritable())
+                                children.append(davxml.Inherited(davxml.HRef(parentURL)))
+                                aces.append(davxml.ACE(*children))
+            else:
+                aces.extend(inherited_aces)
+
+            # Always filter out any remaining private properties when we are
+            # returning the ACL for the final resource after doing parent
+            # inheritance.
+            if not expanding:
+                aces = [
+                    davxml.ACE(*[
+                        c for c in ace.children
+                        if c != TwistedACLInheritable()
+                    ])
+                    for ace in aces
+                ]
+
+            acl = davxml.ACL(*aces)
+
+        yield acl
+
+    accessControlListBatch = deferredGenerator(accessControlListBatch)
+
+class DAVResource (SudoSACLMixin, DAVPropertyHandlerMixin, DAVFastACLMixin, SuperDAVResource, LoggingMixIn):
     """
     Extended L{twisted.web2.dav.resource.DAVResource} implementation.
     """
@@ -419,7 +883,7 @@
             
         yield match
 
-class DAVPrincipalResource (SuperDAVPrincipalResource, LoggingMixIn):
+class DAVPrincipalResource (DAVPropertyHandlerMixin, DAVFastACLMixin, SuperDAVPrincipalResource, LoggingMixIn):
     """
     Extended L{twisted.web2.dav.static.DAVFile} implementation.
     """
@@ -444,10 +908,15 @@
             return davxml.ResourceType(davxml.Principal())
 
 
-class DAVFile (SudoSACLMixin, SuperDAVFile, LoggingMixIn):
+class DAVFile (SudoSACLMixin, DAVPropertyHandlerMixin, DAVFastACLMixin, SuperDAVFile, LoggingMixIn):
     """
     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
@@ -460,7 +929,7 @@
         return super(DAVFile, self).readProperty(property, request)
 
     def resourceType(self):
-        # Allow live property to be overriden by dead property
+        # Allow live property to be overridden by dead property
         if self.deadProperties().contains((dav_namespace, "resourcetype")):
             return self.deadProperties().get((dav_namespace, "resourcetype"))
         if self.isCollection():

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/method/put_common.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/method/put_common.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -523,17 +523,31 @@
         yield response
         response = response.getResult()
 
+        # Reset dead properties
+        if overwrite:
+            destination.update()
+        else:
+            destination.create()
+
+        # Copy dead properties first, before adding overridden values
+        if source is not None:
+            properties = source.deadProperties().getAll()
+            destination.deadProperties().setSeveral([p for p in properties.itervalues()])
+
         # Update the MD5 value on the resource
         if source is not None:
             # Copy MD5 value from source to destination
             if source.hasDeadProperty(TwistedGETContentMD5):
                 md5 = source.readDeadProperty(TwistedGETContentMD5)
                 destination.writeDeadProperty(md5)
+                etag = source.readDeadProperty(davxml.GETETag)
+                destination.writeDeadProperty(etag)
         else:
             # Finish MD5 calc and write dead property
             md5.close()
             md5 = md5.getMD5()
             destination.writeDeadProperty(TwistedGETContentMD5.fromString(md5))
+            destination.writeDeadProperty(davxml.GETETag.fromString(md5))
 
         # Update calendar-access property value on the resource
         if access:
@@ -596,6 +610,9 @@
             # Delete the source resource
             delete(source_uri, source.fp, "0")
             rollback.source_deleted = True
+            # Explicitly delete properties - no one else does it
+            source.deadProperties().deleteAll()
+
             log.debug("Source removed %s" % (source.fp.path,))
 
         def doSourceIndexRecover():

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/root.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/root.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -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/sqlprops-2557/twistedcaldav/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/sql.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/sql.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -40,9 +40,9 @@
     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.
         @type dbpath: str
         @param persistent: C{True} if the data in the DB must be perserved during upgrades,
@@ -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

Copied: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/sqlprops.py (from rev 2558, CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/sqlprops.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/sqlprops.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/sqlprops.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -0,0 +1,634 @@
+##
+# 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
+##
+
+"""
+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.python import log
+from twisted.web2 import responsecode
+from twisted.web2.http import HTTPError, StatusResponse
+
+from twistedcaldav.sql import AbstractSQLDatabase
+
+DEBUG_LOG = False
+
+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.msg("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 getSeveral(self, qnames):
+        """
+        Read specific properties from index.
+        
+        @param qnames: C{list} of C{tuple} of property namespace and name.
+        @return: a C{dict} containing property name/value.
+        """
+        if not qnames:
+            return None
+
+        if not self.index:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No such property: {%s}%s" % qnames[0]
+            ))
+            
+        return self.index.getSeveralPropertyValues(self.rname, qnames)
+
+    def getAll(self, nohidden=True):
+        """
+        Read all properties from index.
+        
+        @param nohidden: C{True} to not 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, nohidden)
+
+    def getSeveralResources(self, qnames):
+        """
+        Read specific properties for all child resources from index.
+        
+        @param qnames: C{list} of C{tuple} of property namespace and name.
+        @return: a C{dict} with resource name as keys and C{dict} of property name/classes as values
+        """
+        if not qnames:
+            return None
+
+        if not self.childindex:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No such property: {%s}%s" % qnames[0]
+            ))
+            
+        return self.childindex.getSeveralResourcePropertyValues(qnames)
+
+    def getAllResources(self, nohidden=True):
+        """
+        Read specific properties for all child resources from index.
+        
+        @param nohidden: C{True} to not return hidden properties, C{False} otherwise.
+        @return: a C{dict} with resource name as keys and C{dict} of property name/classes as values
+        """
+
+        if not self.childindex:
+            raise HTTPError(StatusResponse(
+                responsecode.NOT_FOUND,
+                "No properties"
+            ))
+            
+        return self.childindex.getAllResourcePropertyValues(nohidden)
+
+    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 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 deleteSeveral(self, properties):
+        """
+        Delete property from index.
+
+        DELETE from PROPERTIES where NAME=<<rname>> and PROPNAME=<<pname>> ...
+
+        @param qname:
+        """
+        
+        if self.index:
+            self.index.removeSeveralProperties(self.rname, [p.qname() for p in properties])
+
+    def deleteAll(self):
+        """
+        Delete property 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.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 listAll(self):
+        """
+        List all property names for children of this resource.
+        
+        SELECT RESOURCENAME, PROPNAME from PROPERTIES
+        
+        """
+
+        if self.childindex:
+            return self.childindex.listAllProperties()
+        else:
+            return {}
+
+    def search(self, qname, text):
+        """
+        Search a specific property.
+        
+        @param qname: C{tuple} of property namespace and name.
+        @param text: C{str} containing the text to search for.
+        @return: a C{dict} with a C{str} key for the resource name, and a C{set} of
+            property name C{tuple}'s as values, for each matching property.
+        """
+        if self.index:
+            return self.index.searchOneProperty(qname, text)
+        else:
+            return {}
+
+    def searchSeveral(self, qnames, text):
+        """
+        Search several properties.
+        
+        @param qnames: a C{list} of C{tuple}'s of property namespace and name.
+        @param text: C{str} containing the text to search for.
+        @return: a C{dict} with a C{str} key for the resource name, and a C{set} of
+            property name C{tuple}'s as values, for each matching property.
+        """
+        if self.index:
+            return self.index.searchSeveralProperties(qnames, text)
+        else:
+            return {}
+
+    def searchAll(self, text):
+        """
+        Search all properties.
+        
+        @param text: C{str} containing the text to search for.
+        @return: a C{dict} with a C{str} key for the resource name, and a C{set} of
+            property name C{tuple}'s as values, for each matching property.
+        """
+        if self.index:
+            return self.index.searchAllProperties(text)
+        else:
+            return {}
+
+
+class SQLPropertiesDatabase(AbstractSQLDatabase):
+    """
+    A database to maintain calendar user proxy group memberships.
+
+    SCHEMA:
+    
+    Properties Database:
+    
+    ROW: RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT, PROPERTYVALUE
+    
+    """
+    
+    dbType = "SQLPROPERTIES"
+    dbFilename = ".db.sqlproperties"
+    dbFormatVersion = "1"
+
+    def _encode(cls, name):
+        return "{%s}%s" % name
+
+    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:])
+
+    _encode = classmethod(_encode)
+    _decode = classmethod(_decode)
+
+    def __init__(self, path):
+        path = os.path.join(path, SQLPropertiesDatabase.dbFilename)
+        super(SQLPropertiesDatabase, self).__init__(path, True, utf8=True)
+
+    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 hidden: C{True} for a hidden property, C{False} otherwise.
+        @param pvalue: a C{str} containing the property value to set.
+        """
+        
+        # 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()
+
+    def setSeveralPropertyValues(self, rname, properties):
+        """
+        Add a set of properties.
+    
+        @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.
+        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 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: a C{str} containing the property value.
+        """
+        
+        # Remove what is there, then add it back.
+        if DEBUG_LOG:
+            log.msg("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:
+            return None
+        elif setlength == 1:
+            return cPickle.loads(members[0])
+        else:
+            raise ValueError("Multiple properties of the same name \"%s\" stored for resource \"%s\"" % (pname, rname,))
+
+    def getSeveralPropertyValues(self, rname, pnames):
+        """
+        Get specified property values from specific resource.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pnames: a C{list} of C{str} containing the name of the properties to get.
+        @return: a C{dict} containing property name/value.
+        """
+        
+        # Remove what is there, then add it back.
+        if DEBUG_LOG:
+            log.msg("getSeveralPropertyValues: %s \"%s\"" % (self.dbpath, pnames))
+        properties = {}
+        statement = "select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1 and PROPERTYNAME in ("
+        args = [rname]
+        for i, pname in enumerate(pnames):
+            if i != 0:
+                statement += ", "
+            statement += ":%s" % (i + 2,)
+            args.append(self._encode(pname))
+        statement += ")"
+
+        for row in self._db_execute(statement, *args):
+            properties[self._decode(row[0])] = cPickle.loads(row[1])
+
+        return properties
+
+    def getAllPropertyValues(self, rname, nohidden):
+        """
+        Get specified property values from specific resource.
+    
+        @param rname: a C{str} containing the resource name.
+        @param nohidden: C{True} to not return hidden properties, C{False} otherwise.
+        @return: a C{dict} containing property name/value.
+        """
+        
+        properties = {}
+        if nohidden:
+            statement = "select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1 and HIDDEN = 'F'"
+        else:
+            statement = "select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1"
+        for row in self._db_execute(statement, rname):
+            properties[self._decode(row[0])] = cPickle.loads(row[1])
+
+        return properties
+
+    def getSeveralResourcePropertyValues(self, pnames):
+        """
+        Get specified property values from all resources.
+    
+        @param pnames: a C{list} of C{str} containing the name of the properties to get.
+        @return: a C{dict} containing C{str} keys (names of child resources) and C{dict} values of property name/value.
+        """
+        
+        # Remove what is there, then add it back.
+        if DEBUG_LOG:
+            log.msg("getAllPropertyValues: %s \"%s\"" % (self.dbpath, pnames))
+        members = {}
+        statement = "select RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where PROPERTYNAME in ("
+        args = []
+        for i, pname in enumerate(pnames):
+            if i != 0:
+                statement += ", "
+            statement += ":%s" % (i + 1,)
+            args.append(self._encode(pname))
+        statement += ")"
+
+        for row in self._db_execute(statement, *args):
+            members.setdefault(row[0], {})[self._decode(row[1])] = cPickle.loads(row[2])
+
+        return members
+
+    def getAllResourcePropertyValues(self, nohidden):
+        """
+        Get specified property values from all resources.
+    
+        @param nohidden: C{True} to not return hidden properties, C{False} otherwise.
+        @return: a C{dict} containing C{str} keys (names of child resources) and C{dict} values of property name/value.
+        """
+        
+        members = {}
+        if nohidden:
+            statement = "select RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where HIDDEN='F'"
+        else:
+            statement = "select RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT from PROPERTIES"
+        for row in self._db_execute(statement):
+            members.setdefault(row[0], {})[self._decode(row[1])] = cPickle.loads(row[2])
+
+        return members
+
+    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.
+        @return: a C{str} containing the property value.
+        """
+
+        self._delete_from_db(rname, self._encode(pname))
+        self._db_commit()
+
+    def removeSeveralProperties(self, rname, pnames):
+        """
+        Remove specified properties.
+    
+        @param rname: a C{str} containing the resource name.
+        @param pnames: a C{list} containing the names of the properties to remove.
+        @return: a C{str} containing the property value.
+        """
+
+        for pname in pnames:
+            self._delete_from_db(rname, self._encode(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(self._decode(row[0]))
+        return members
+
+    def listAllProperties(self):
+        """
+        List all properties in child resources.
+    
+        @param rname: a C{str} containing the resource name.
+        @return: a C{dict} containing the resource names and property names.
+        """
+
+        results = {}
+        for row in self._db_execute("select RESOURCENAME, PROPERTYNAME from PROPERTIES"):
+            results.setdefault(row[0], set()).add(self._decode(row[1]))
+        return results
+
+    def searchOneProperty(self, pname, text):
+        results = {}
+        liketext = "%%%s%%" % (text,)
+        for row in self._db_execute("select RESOURCENAME, PROPERTYNAME from PROPERTIES where PROPERTYNAME = :1 and PROPERTYVALUE like :2", self._encode(pname), liketext):
+            results.setdefault(row[0], set()).add(self._decode(row[1]))
+        return results
+
+    def searchSeveralProperties(self, pnames, text):
+        results = {}
+        liketext = "%%%s%%" % (text,)
+        statement = "select RESOURCENAME, PROPERTYNAME from PROPERTIES where ("
+        args = []
+        count = 1
+        for p in pnames:
+            if count != 1:
+                statement += " or "
+            statement += "PROPERTYNAME = :%s" % (count,)
+            args.append(self._encode(p))
+            count += 1
+        statement += ") and PROPERTYVALUE like :%s" % (count,)
+        args.append(liketext)
+        for row in self._db_execute(statement, *args):
+            results.setdefault(row[0], set()).add(self._decode(row[1]))
+        return results
+
+    def searchAllProperties(self, text):
+        results = {}
+        liketext = "%%%s%%" % (text,)
+        for row in self._db_execute("select RESOURCENAME, PROPERTYNAME from PROPERTIES where PROPERTYVALUE like :1", liketext):
+            results.setdefault(row[0], set()).add(self._decode(row[1]))
+        return results
+
+    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.
+        """
+        
+        if hidden:
+            hidden_value = "T"
+        else:
+            hidden_value = "F"
+        self._db_execute(
+            """
+            insert into PROPERTIES (RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT, PROPERTYVALUE, HIDDEN)
+            values (:1, :2, :3, :4, :5)
+            """, rname, pname, pobject, pvalue, hidden_value
+        )
+       
+    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_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)
+            )
+            """
+        )

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/static.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/static.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -61,6 +61,7 @@
 from twistedcaldav.index import Index, IndexSchedule
 from twistedcaldav.resource import CalDAVResource, isCalendarCollectionResource, isPseudoCalendarCollectionResource
 from twistedcaldav.schedule import ScheduleInboxResource, ScheduleOutboxResource
+from twistedcaldav.sqlprops import sqlPropertyStore, SQLPropertiesDatabase
 from twistedcaldav.dropbox import DropBoxHomeResource, DropBoxCollectionResource
 from twistedcaldav.directory.calendar import uidsResourceName
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
@@ -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
 
@@ -162,6 +164,7 @@
             if status != responsecode.CREATED:
                 raise HTTPError(status)
 
+            self.create()
             self.writeDeadProperty(resourceType)
             return status
 
@@ -440,6 +443,8 @@
             fp.open("w").close()
             fp.restat(False)
 
+        self.create()
+
         return True
 
 class CalendarHomeProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeProvisioningResource, DAVFile):

Modified: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/tap.py	2008-06-17 03:54:13 UTC (rev 2568)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/tap.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -521,7 +521,7 @@
         root.putChild('principals', principalCollection)
         root.putChild('calendars', calendarCollection)
 
-		# Timezone service is optional
+        # Timezone service is optional
         if config.EnableTimezoneService:
             timezoneService = self.timezoneServiceResourceClass(
                 os.path.join(config.DocumentRoot, "timezones"),
@@ -529,6 +529,8 @@
             )
             root.putChild('timezones', timezoneService)
 
+        root.create()
+
         # Configure default ACLs on the root resource
 
         log.info("Setting up default ACEs on root resource")

Copied: CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/test/test_sqlprops.py (from rev 2558, CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/test/test_sqlprops.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/test/test_sqlprops.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sqlprops-2557/twistedcaldav/test/test_sqlprops.py	2008-06-17 14:27:41 UTC (rev 2569)
@@ -0,0 +1,496 @@
+##
+# 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.ical import Component
+from twisted.web2.dav.element.rfc2518 import DisplayName
+
+import os
+import time
+
+from twisted.trial.unittest import SkipTest
+from twisted.web2 import responsecode
+from twisted.web2.dav import davxml
+from twisted.web2.dav.fileop import put
+from twisted.web2.dav.resource import TwistedGETContentMD5
+from twisted.web2.dav.test.util import serialize
+from twisted.web2.iweb import IResponse
+from twisted.web2.stream import MemoryStream
+from twisted.web2.test.test_server import SimpleRequest
+
+from twistedcaldav import caldavxml, customxml
+from twistedcaldav.root import RootResource
+from twistedcaldav.sqlprops import sqlPropertyStore, SQLPropertiesDatabase
+from twistedcaldav.static import CalDAVFile
+import twistedcaldav.test.util
+
+class SQLProps (twistedcaldav.test.util.TestCase):
+    """
+    SQL properties tests
+    """
+    data_dir = os.path.join(os.path.dirname(__file__), "data")
+
+    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, description = ""):
+        self.assertTrue(index.contains(prop.qname()),
+                        msg="Could not find property %s %s." % (description, prop,))
+        self.assertTrue(index.get(prop.qname()) == prop,
+                        msg="Could not get property %s %s." % (description, prop,))
+    
+    def _testPropertyList(self, proplist):
+        self.assertTrue(len(proplist) == len(SQLProps.props),
+                        msg="Number of properties returned %s not equal to number queried %s." % (len(proplist), len(SQLProps.props),))
+        for prop in SQLProps.props:
+            for k, v in proplist.iteritems():
+                if prop == v:
+                    del proplist[k]
+                    break
+        self.assertTrue(len(proplist) == 0,
+                        msg="Incorrect properties returned %s." % proplist)
+
+    def _testResourcePropertyList(self, num_resources, resourcedict):
+        self.assertTrue(len(resourcedict) == num_resources,
+                        msg="Number of resources returned %s not equal to number queried %s." % (len(resourcedict), num_resources,))
+        for i in xrange(num_resources):
+            fname = "file%04s.ics" % (i,)
+            self.assertTrue(resourcedict.has_key(fname),
+                            msg="Resource %s not returned in query results" % (fname,))
+            self._testPropertyList(resourcedict[fname])
+
+    def _setupMultipleResources(self, number):
+        self.collection_name, self.collection_uri = self.mkdtemp("sql")
+        for i in xrange(number):
+            rsrc = CalDAVFile(os.path.join(self.collection_name, "file%04s.ics" % (i,)))
+            index = sqlPropertyStore(rsrc)
+            index.setSeveral(SQLProps.props)
+        return index
+
+    def _setupMultipleDifferentResources(self, number):
+        self.collection_name, self.collection_uri = self.mkdtemp("sql")
+        for i in xrange(number):
+            rsrc = CalDAVFile(os.path.join(self.collection_name, "file%04s.ics" % (i,)))
+            index = sqlPropertyStore(rsrc)
+            props = (
+                davxml.DisplayName.fromString("My Name %s" % (i,)),
+                davxml.ACL(
+                    davxml.ACE(
+                        davxml.Principal(davxml.HRef.fromString("/principals/users/user%s" % (i + 30,))),
+                        davxml.Grant(davxml.Privilege(davxml.Read())),
+                        davxml.Protected(),
+                    ),
+                ),
+                caldavxml.CalendarDescription.fromString("My Calendar %s" % (i + 50,)),
+                customxml.TwistedScheduleAutoRespond(),
+            )
+            index.setSeveral(props)
+        return index
+
+    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(os.path.dirname(self.collection_name), SQLPropertiesDatabase.dbFilename)),
+                        msg="Could not initialize index via collection resource.")
+
+    def test_db_init_root(self):
+        self.collection_name, self.collection_uri = self.mkdtemp("sql")
+        rsrc = RootResource(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_setunknownproperty(self):
+        doc = davxml.WebDAVDocument.fromString("""<?xml version="1.0" encoding="utf-8" ?><guess/>""")
+        self._setOnePropertyAndTest(doc.root_element)
+
+    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_setallproperties(self):
+        index = self._setUpIndex()
+        index.setSeveral(SQLProps.props)
+        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_deleteseveralproperties(self):
+        index = self._setUpIndex()
+        for prop in SQLProps.props:
+            self._setProperty(index, prop)
+        
+        delete_props = SQLProps.props[:2]
+        remaining_props = SQLProps.props[2:]
+        index.deleteSeveral(delete_props)
+        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)
+
+    def test_getallproperties(self):
+        index = self._setUpIndex()
+        for prop in SQLProps.props:
+            self._setProperty(index, prop)
+        
+        result = index.getSeveral([p.qname() for p in SQLProps.props])
+        self._testPropertyList(result)
+
+    def test_getallresourceproperties(self):
+        num_resources = 10
+        index = self._setupMultipleResources(num_resources)
+        result = index.getSeveralResources([p.qname() for p in SQLProps.props])
+        self._testResourcePropertyList(num_resources, result)
+
+#    def test_timegetallresourceproperties(self):
+#        num_resources = 1000
+#        index = self._setupMultipleResources(num_resources)
+#        t1 = time.time()
+#        result = index.getSeveralResources([p.qname() for p in SQLProps.props])
+#        t2 = time.time()
+#        self.assertTrue(t1 == t2,
+#                        msg="Time for 1000 prop query = %s" % (t2 - t1,))
+#
+#        self._testResourcePropertyList(num_resources, result)
+
+    def _do_delete(self, parent):
+        fpath = self.docroot
+        if parent:
+            fpath = os.path.join(fpath, parent)
+        fpath = os.path.join(fpath, "file.ics")
+        rsrc = CalDAVFile(fpath)
+        ms = MemoryStream("Some Data")
+
+        def donePut(status):
+            self.assertTrue(status == responsecode.CREATED)
+            md5 = TwistedGETContentMD5.fromString("MD5")
+            rsrc.writeDeadProperty(md5)
+            
+            # Check index
+            index = sqlPropertyStore(rsrc)
+            self._testProperty(index, md5)
+            
+            def doneDelete(response):
+                response = IResponse(response)
+    
+                if response.code != responsecode.NO_CONTENT:
+                    self.fail("DELETE response %s != %s" % (response.code, responsecode.NO_CONTENT))
+    
+                if os.path.exists(fpath):
+                    self.fail("DELETE did not remove path %s" % (fpath,))
+
+                self.assertFalse(index.contains(md5.qname()),
+                                 msg="Property %s exists after resource was deleted." % md5)
+
+            def work():
+                # Delete resource and test
+                request = SimpleRequest(self.site, "DELETE", "/%sfile.ics" % (parent,))
+                yield (request, doneDelete)
+
+            return serialize(self.send, work())
+
+        d = put(ms, rsrc.fp)
+        d.addCallback(donePut)
+        return d
+
+    def test_deleteresource(self):
+        return self._do_delete("")
+
+    def test_deletecalendarresource(self):
+
+        def doneMake(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            return self._do_delete("calendar/")
+
+        # Make a calendar
+        request = SimpleRequest(self.site, "MKCALENDAR", "/calendar/")
+        return self.send(request, doneMake)
+
+    event = """BEGIN:VCALENDAR
+PRODID:Test Case
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+DTSTAMP:20070219T120000Z
+DTSTART:20070219T120000Z
+DTEND:20070219T130000Z
+UID:12345-67890-54321
+END:VEVENT
+END:VCALENDAR
+"""
+
+    def _do_copy(self, src, dst):
+        fpath = self.docroot
+        if src:
+            fpath = os.path.join(fpath, src)
+        fpath = os.path.join(fpath, "file.ics")
+        fpath_new = self.docroot
+        if dst:
+            fpath_new = os.path.join(fpath_new, dst)
+        fpath_new = os.path.join(fpath_new, "copy.ics")
+        rsrc = CalDAVFile(fpath)
+
+        def donePut(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            displayname = DisplayName.fromString("adisplayname")
+            rsrc.writeDeadProperty(displayname)
+            
+            # Check index
+            index = sqlPropertyStore(rsrc)
+            self._testProperty(index, displayname)
+            
+            def doneCopy(response):
+                response = IResponse(response)
+    
+                if response.code != responsecode.CREATED:
+                    self.fail("COPY response %s != %s" % (response.code, responsecode.NO_CONTENT))
+            
+                if not os.path.exists(fpath):
+                    self.fail("COPY removed original path %s" % (fpath,))
+
+                if not os.path.exists(fpath_new):
+                    self.fail("COPY did not create new path %s" % (fpath_new,))
+
+                self._testProperty(index, displayname, "on original resource")
+
+                rsrc_new = CalDAVFile(fpath_new)
+                index_new = sqlPropertyStore(rsrc_new)
+                self._testProperty(index_new, displayname, "on new resource")
+
+            # Copy resource and test
+            request = SimpleRequest(self.site, "COPY", "/%sfile.ics" % (src,))
+            request.headers.setHeader("destination", "/%scopy.ics" % (dst,))
+            return self.send(request, doneCopy)
+
+        stream = file(os.path.join(self.data_dir, "Holidays", "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"))
+        try: calendar = str(Component.fromStream(stream))
+        finally: stream.close()
+
+        request = SimpleRequest(self.site, "PUT", "/%sfile.ics" % (src,))
+        request.stream = MemoryStream(calendar)
+        return self.send(request, donePut)
+    
+    def test_copyresource(self):
+        return self._do_copy("", "")
+
+    def test_copycalendarresource(self):
+
+        def doneMake2(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            return self._do_copy("calendar1/", "calendar2/")
+
+        def doneMake1(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            request = SimpleRequest(self.site, "MKCALENDAR", "/calendar2/")
+            return self.send(request, doneMake2)
+
+        # Make a calendar
+        request = SimpleRequest(self.site, "MKCALENDAR", "/calendar1/")
+        return self.send(request, doneMake1)
+
+    def _do_move(self, src, dst):
+        fpath = self.docroot
+        if src:
+            fpath = os.path.join(fpath, src)
+        fpath = os.path.join(fpath, "file.ics")
+        fpath_new = self.docroot
+        if dst:
+            fpath_new = os.path.join(fpath_new, dst)
+        fpath_new = os.path.join(fpath_new, "move.ics")
+        rsrc = CalDAVFile(fpath)
+
+        def donePut(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            displayname = DisplayName.fromString("adisplayname")
+            rsrc.writeDeadProperty(displayname)
+            
+            # Check index
+            index = sqlPropertyStore(rsrc)
+            self._testProperty(index, displayname)
+            
+            def doneMove(response):
+                response = IResponse(response)
+    
+                if response.code != responsecode.CREATED:
+                    self.fail("MOVE response %s != %s" % (response.code, responsecode.NO_CONTENT))
+            
+                if os.path.exists(fpath):
+                    self.fail("MOVE did not remove original path %s" % (fpath,))
+
+                if not os.path.exists(fpath_new):
+                    self.fail("MOVE did not create new path %s" % (fpath_new,))
+
+                self.assertFalse(index.contains(displayname.qname()),
+                                 msg="Property %s exists after resource was moved." % displayname)
+
+                rsrc_new = CalDAVFile(fpath_new)
+                index_new = sqlPropertyStore(rsrc_new)
+                self._testProperty(index_new, displayname, "on new resource")
+
+            def work():
+                # Move resource and test
+                request = SimpleRequest(self.site, "MOVE", "/%sfile.ics" % (src,))
+                request.headers.setHeader("destination", "/%smove.ics" % (dst,))
+                yield (request, doneMove)
+
+            return serialize(self.send, work())
+
+        stream = file(os.path.join(self.data_dir, "Holidays", "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics"))
+        try: calendar = str(Component.fromStream(stream))
+        finally: stream.close()
+
+        request = SimpleRequest(self.site, "PUT", "/%sfile.ics" % (src,))
+        request.stream = MemoryStream(calendar)
+        return self.send(request, donePut)
+    
+    def test_moveresource(self):
+        return self._do_move("", "")
+
+    def test_movecalendarresource(self):
+
+        def doneMake2(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            return self._do_move("calendar1/", "calendar2/")
+
+        def doneMake1(response):
+            self.assertTrue(response.code == responsecode.CREATED)
+            request = SimpleRequest(self.site, "MKCALENDAR", "/calendar2/")
+            return self.send(request, doneMake2)
+
+        # Make a calendar
+        request = SimpleRequest(self.site, "MKCALENDAR", "/calendar1/")
+        return self.send(request, doneMake1)
+
+    def test_searchone(self):
+        index = self._setupMultipleDifferentResources(20)
+        results = index.search(davxml.DisplayName.qname(), "2")
+        expected = {
+            "file   2.ics" : set((davxml.DisplayName.qname(),)),
+            "file  12.ics" : set((davxml.DisplayName.qname(),)),
+        }
+        self.assertTrue(results == expected,
+            msg="Search results %s != %s" % (results, expected,))
+    
+    def test_searchseveral(self):
+        index = self._setupMultipleDifferentResources(20)
+        results = index.searchSeveral((davxml.DisplayName.qname(), davxml.ACL.qname()), "3")
+        expected = {
+            "file   0.ics" : set((davxml.ACL.qname(),)),
+            "file   1.ics" : set((davxml.ACL.qname(),)),
+            "file   2.ics" : set((davxml.ACL.qname(),)),
+            "file   3.ics" : set((davxml.DisplayName.qname(), davxml.ACL.qname(),)),
+            "file   4.ics" : set((davxml.ACL.qname(),)),
+            "file   5.ics" : set((davxml.ACL.qname(),)),
+            "file   6.ics" : set((davxml.ACL.qname(),)),
+            "file   7.ics" : set((davxml.ACL.qname(),)),
+            "file   8.ics" : set((davxml.ACL.qname(),)),
+            "file   9.ics" : set((davxml.ACL.qname(),)),
+            "file  13.ics" : set((davxml.DisplayName.qname(), davxml.ACL.qname(),)),
+        }
+        self.assertTrue(results == expected,
+            msg="Search results %s != %s" % (results, expected,))
+    
+    def test_searchall(self):
+        index = self._setupMultipleDifferentResources(20)
+        results = index.searchAll("5")
+        expected = {
+            "file   0.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   1.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   2.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   3.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   4.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   5.ics" : set((davxml.DisplayName.qname(), caldavxml.CalendarDescription.qname(), davxml.ACL.qname(),)),
+            "file   6.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   7.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   8.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file   9.ics" : set((caldavxml.CalendarDescription.qname(),)),
+            "file  15.ics" : set((davxml.DisplayName.qname(), caldavxml.CalendarDescription.qname(), davxml.ACL.qname(),)),
+        }
+        self.assertTrue(results == expected,
+            msg="Search results %s != %s" % (results, expected,))
+        pass

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


More information about the calendarserver-changes mailing list