[CalendarServer-changes] [1295] CalendarServer/branches/users/cdaboo/sqlprops-1202

source_changes at macosforge.org source_changes at macosforge.org
Wed Feb 28 14:06:41 PST 2007


Revision: 1295
          http://trac.macosforge.org/projects/calendarserver/changeset/1295
Author:   cdaboo at apple.com
Date:     2007-02-28 14:06:40 -0800 (Wed, 28 Feb 2007)

Log Message:
-----------
SQL property database implementation that does a "batch" query on all child resources. Still a work in progress.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.element.base.patch
    CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/extensions.py
    CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/sqlprops.py
    CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/static.py
    CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/tap.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.element.base.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.element.base.patch	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.element.base.patch	2007-02-28 22:06:40 UTC (rev 1295)
@@ -2,6 +2,15 @@
 ===================================================================
 --- twisted/web2/dav/element/base.py	(revision 18545)
 +++ twisted/web2/dav/element/base.py	(working copy)
+@@ -45,7 +45,7 @@
+ ]
+ 
+ import string
+-import StringIO
++import cStringIO
+ import xml.dom.minidom
+ 
+ import datetime
 @@ -145,21 +145,20 @@
  
          if self.allowed_attributes:
@@ -123,10 +132,11 @@
 +        # Quote any single quotes. We do not need to be any smarter than this.
 +        value = value.replace("'", "'")
 +
-+        output.write(" %s='%s'" % (name, value,))  
++        output.write(" %s='%s'" % (name, value,))
 +      
      def toxml(self):
-         output = StringIO.StringIO()
+-        output = StringIO.StringIO()
++        output = cStringIO.StringIO()
          self.writeXML(output)
 -        return output.getvalue()
 +        return str(output.getvalue())
@@ -147,7 +157,7 @@
 +            if "&" in cdata:
 +                cdata = cdata.replace("&", "&")
 +            if "<" in cdata:
-+                cdata = cdata.replace("&", "&lt;")
++                cdata = cdata.replace("<", "&lt;")
 +            if "]]>" in cdata:
 +                cdata = cdata.replace("]]>", "]]&lt;")
 +

Added: CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	2007-02-28 22:06:40 UTC (rev 1295)
@@ -0,0 +1,280 @@
+Index: twisted/web2/dav/method/propfind.py
+===================================================================
+--- twisted/web2/dav/method/propfind.py	(revision 18545)
++++ twisted/web2/dav/method/propfind.py	(working copy)
+@@ -22,6 +22,8 @@
+ #
+ # DRI: Wilfredo Sanchez, wsanchez at apple.com
+ ##
++from twisted.web2.dav.util import joinURL
++import urllib
+ 
+ """
+ WebDAV PROPFIND method
+@@ -114,79 +116,205 @@
+     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()
++#    d = self.findChildrenBatch(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()
+ 
+-    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: [p for p in results[uri].itervalues()]
++            }
++            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)
++    
++    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
++
++        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(prop))
+ 
+-            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)
++    
++#    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
++#
++#            properties_by_status = {
++#                responsecode.OK: [propertyName(p) for p in resource_properties]
++#            }
++#        else:
++#            properties_by_status = {
++#                responsecode.OK        : [],
++#                responsecode.NOT_FOUND : [],
++#            }
++#
++#            if search_properties is "all":
++#                properties_to_enumerate = waitForDeferred(resource.listAllprop(request))
++#                yield properties_to_enumerate
++#                properties_to_enumerate = properties_to_enumerate.getResult()
++#            else:
++#                properties_to_enumerate = search_properties
++#
++#            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)
++#                else:
++#                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(property))
++#
++#        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)
+ 
+-            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)
+-                else:
+-                    properties_by_status[responsecode.NOT_FOUND].append(propertyName(property))
+-
+-        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
+     #

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.resource.patch	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/lib-patches/Twisted/twisted.web2.dav.resource.patch	2007-02-28 22:06:40 UTC (rev 1295)
@@ -81,7 +81,7 @@
  
                  def ifAllowed(privileges, callback):
                      def onError(failure):
-@@ -286,7 +307,33 @@
+@@ -286,14 +307,36 @@
                          d.addCallback(gotACL)
                          return d
                      return ifAllowed((davxml.ReadACL(),), callback)
@@ -114,8 +114,16 @@
 +
              elif namespace == twisted_dav_namespace:
                  if name == "resource-class":
-                     class ResourceClass (davxml.WebDAVTextElement):
-@@ -363,15 +410,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 +406,28 @@
          """
          See L{IDAVResource.listProperties}.
          """
@@ -148,7 +156,7 @@
      def listAllprop(self, request):
          """
          Some DAV properties should not be returned to a C{DAV:allprop} query.
-@@ -465,8 +525,22 @@
+@@ -465,8 +521,22 @@
              return super(DAVPropertyMixIn, self).displayName()
  
  class DAVResource (DAVPropertyMixIn, StaticRenderMixin):
@@ -171,7 +179,7 @@
      ##
      # DAV
      ##
-@@ -509,6 +583,9 @@
+@@ -509,6 +579,9 @@
              reactor.callLater(0, getChild)
  
          def checkPrivileges(child):
@@ -181,7 +189,7 @@
              if privileges is None:
                  return child
     
-@@ -517,14 +594,17 @@
+@@ -517,14 +590,17 @@
              return d
  
          def gotChild(child, childpath):
@@ -206,7 +214,7 @@
  
              reactor.callLater(0, getChild)
  
-@@ -535,10 +615,10 @@
+@@ -535,10 +611,10 @@
                  completionDeferred.callback(None)
              else:
                  childpath = joinURL(basepath, childname)
@@ -221,7 +229,7 @@
  
          getChild()
  
-@@ -564,19 +644,21 @@
+@@ -564,19 +640,21 @@
          See L{IDAVResource.authorize}.
          """
          def onError(failure):
@@ -248,7 +256,7 @@
                      response = UnauthorizedResponse(request.credentialFactories,
                                                      request.remoteAddr)
                  else:
-@@ -587,7 +669,7 @@
+@@ -587,7 +665,7 @@
                  # class is supposed to be a FORBIDDEN status code and
                  # "Authorization will not help" according to RFC2616
                  #
@@ -257,7 +265,7 @@
  
              d = self.checkPrivileges(request, privileges, recurse)
              d.addErrback(onErrors)
-@@ -600,16 +682,21 @@
+@@ -600,16 +678,21 @@
  
      def authenticate(self, request):
          def loginSuccess(result):
@@ -283,7 +291,7 @@
  
          authHeader = request.headers.getHeader('authorization')
  
-@@ -625,9 +712,10 @@
+@@ -625,9 +708,10 @@
  
                  # Try to match principals in each principal collection on the resource
                  def gotDetails(details):
@@ -297,7 +305,7 @@
  
                  def login(pcreds):
                      d = request.portal.login(pcreds, None, *request.loginInterfaces)
-@@ -635,13 +723,15 @@
+@@ -635,13 +719,15 @@
  
                      return d
  
@@ -317,7 +325,7 @@
  
      ##
      # ACL
-@@ -650,49 +740,23 @@
+@@ -650,49 +736,23 @@
      def currentPrincipal(self, request):
          """
          @param request: the request being processed.
@@ -376,7 +384,7 @@
          """
          @return: the L{davxml.ACL} element containing the default access control
              list for this resource.
-@@ -704,6 +768,17 @@
+@@ -704,6 +764,17 @@
          #
          return readonlyACL
  
@@ -394,7 +402,7 @@
      def setAccessControlList(self, acl):
          """
          See L{IDAVResource.setAccessControlList}.
-@@ -1032,9 +1107,9 @@
+@@ -1032,9 +1103,9 @@
  
              if myURL == "/":
                  # If we get to the root without any ACLs, then use the default.
@@ -406,7 +414,7 @@
  
          # Dynamically update privileges for those ace's that are inherited.
          if inheritance:
-@@ -1070,7 +1145,7 @@
+@@ -1070,7 +1141,7 @@
                                  # Adjust ACE for inherit on this resource
                                  children = list(ace.children)
                                  children.remove(TwistedACLInheritable())
@@ -415,7 +423,7 @@
                                  aces.append(davxml.ACE(*children))
              else:
                  aces.extend(inherited_aces)
-@@ -1122,7 +1197,7 @@
+@@ -1122,7 +1193,7 @@
                  # Adjust ACE for inherit on this resource
                  children = list(ace.children)
                  children.remove(TwistedACLInheritable())
@@ -424,7 +432,7 @@
                  aces.append(davxml.ACE(*children))
                  
          # Filter out those that do not have a principal match with the current principal
-@@ -1146,49 +1221,69 @@
+@@ -1146,49 +1217,69 @@
  
          This implementation returns an empty set.
          """
@@ -522,7 +530,7 @@
      def samePrincipal(self, principal1, principal2):
          """
          Check whether the two prinicpals are exactly the same in terms of
-@@ -1213,7 +1308,6 @@
+@@ -1213,7 +1304,6 @@
              return False
                  
      def matchPrincipal(self, principal1, principal2, request):
@@ -530,7 +538,7 @@
          """
          Check whether the principal1 is a principal in the set defined by
          principal2.
-@@ -1238,6 +1332,9 @@
+@@ -1238,6 +1328,9 @@
              if isinstance(principal1, davxml.Unauthenticated):
                  yield False
                  return
@@ -540,7 +548,7 @@
              else:
                  yield True
                  return
-@@ -1265,7 +1362,6 @@
+@@ -1265,7 +1358,6 @@
  
          assert principal2 is not None, "principal2 is None"
  
@@ -548,7 +556,7 @@
          # Compare two HRefs and do group membership test as well
          if principal1 == principal2:
              yield True
-@@ -1296,9 +1392,9 @@
+@@ -1296,9 +1388,9 @@
          def testGroup(group):
              # Get principal resource for principal2
              if group and isinstance(group, DAVPrincipalResource):
@@ -561,7 +569,7 @@
                  
              return False
  
-@@ -1426,7 +1522,7 @@
+@@ -1426,7 +1518,7 @@
                  log.err("DAV:self ACE is set on non-principal resource %r" % (self,))
                  yield None
                  return
@@ -570,7 +578,7 @@
  
          if isinstance(principal, davxml.HRef):
              yield principal
-@@ -1511,6 +1607,265 @@
+@@ -1511,6 +1603,265 @@
          return None
  
      ##
@@ -836,7 +844,7 @@
      # HTTP
      ##
  
-@@ -1558,10 +1913,10 @@
+@@ -1558,10 +1909,10 @@
      """
      DAV resource with no children.
      """
@@ -849,7 +857,7 @@
      """
      Resource representing a WebDAV principal.  (RFC 3744, section 2)
      """
-@@ -1571,7 +1926,7 @@
+@@ -1571,7 +1922,7 @@
      # WebDAV
      ##
  
@@ -858,7 +866,7 @@
          (dav_namespace, "alternate-URI-set"),
          (dav_namespace, "principal-URL"    ),
          (dav_namespace, "group-member-set" ),
-@@ -1579,14 +1934,11 @@
+@@ -1579,14 +1930,11 @@
      )
  
      def davComplianceClasses(self):
@@ -874,7 +882,7 @@
      def readProperty(self, property, request):
          def defer():
              if type(property) is tuple:
-@@ -1604,10 +1956,10 @@
+@@ -1604,10 +1952,10 @@
                      return davxml.PrincipalURL(davxml.HRef(self.principalURL()))
  
                  if name == "group-member-set":
@@ -887,7 +895,7 @@
  
                  if name == "resourcetype":
                      if self.isCollection():
-@@ -1673,6 +2025,24 @@
+@@ -1673,6 +2021,24 @@
          else:
              return uri in self.groupMembers()
  
@@ -912,7 +920,7 @@
  class AccessDeniedError(Exception):
      def __init__(self, errors):
          """ 
-@@ -1712,6 +2082,37 @@
+@@ -1712,6 +2078,48 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  
@@ -947,6 +955,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-1202/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/extensions.py	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/extensions.py	2007-02-28 22:06:40 UTC (rev 1295)
@@ -15,7 +15,17 @@
 #
 # DRI: Wilfredo Sanchez, wsanchez at apple.com
 ##
+from twisted.web2.dav.element.base import dav_namespace
 
+from twisted.web2.dav.util import parentForURL
+from twisted.web2.dav.resource import TwistedACLInheritable
+from twisted.web2.dav.util import joinURL
+from twisted.internet import reactor
+from twisted.web2.dav.resource import AccessDeniedError
+from twisted.internet.defer import Deferred
+from twisted.web2.http_headers import generateContentType
+from twistedcaldav.sqlprops import sqlPropertyStore
+
 """
 Extensions to web2.dav
 """
@@ -29,6 +39,7 @@
 
 import urllib
 import cgi
+import os
 import time
 
 from twisted.python import log
@@ -44,6 +55,7 @@
 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.resource import TwistedResourceClass
 from twistedcaldav.directory.sudo import SudoDirectoryService
 
 
@@ -69,13 +81,475 @@
         return super(SudoAuthIDMixin, self).findPrincipalForAuthID(authid)
 
 
-class DAVResource (SudoAuthIDMixin, SuperDAVResource):
+class DAVPropertyHandlerMixin(object):
+
+    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):
+        
+        from twistedcaldav.resource import AccessDisabled
+        from twistedcaldav.static import calendarPrivilegeSet
+
+        # Get aces for children
+        acls = self.deadProperties().getSeveralResources((
+            davxml.ACL.qname(),
+            TwistedResourceClass.qname(),
+            davxml.GETETag.qname(),
+            AccessDisabled.qname(),
+        ))
+        aclmap = {}
+        for name, props in acls.iteritems():
+            if props.has_key(AccessDisabled.qname()):
+                if callbackForbidden:
+                    callbackForbidden(name)
+                continue
+            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 (SudoAuthIDMixin, DAVPropertyHandlerMixin, DAVFastACLMixin, SuperDAVResource):
     """
     Extended L{twisted.web2.dav.resource.DAVResource} implementation.
     """
 
-
-class DAVPrincipalResource (SuperDAVPrincipalResource):
+class DAVPrincipalResource (DAVPropertyHandlerMixin, DAVFastACLMixin, SuperDAVPrincipalResource):
     """
     Extended L{twisted.web2.dav.static.DAVFile} implementation.
     """
@@ -100,10 +574,15 @@
             return davxml.ResourceType(davxml.Principal())
 
 
-class DAVFile (SudoAuthIDMixin, SuperDAVFile):
+class DAVFile (SudoAuthIDMixin, DAVPropertyHandlerMixin, DAVFastACLMixin, SuperDAVFile):
     """
     Extended L{twisted.web2.dav.static.DAVFile} implementation.
     """
+    def deadProperties(self):
+        if not hasattr(self, "_dead_properties"):
+            self._dead_properties = sqlPropertyStore(self)
+        return self._dead_properties
+
     def readProperty(self, property, request):
         if type(property) is tuple:
             qname = property

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/method/put_common.py	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/method/put_common.py	2007-02-28 22:06:40 UTC (rev 1295)
@@ -434,6 +434,12 @@
         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()
@@ -445,11 +451,14 @@
             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))
 
         response = IResponse(response)
         

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/sqlprops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/sqlprops.py	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/sqlprops.py	2007-02-28 22:06:40 UTC (rev 1295)
@@ -38,7 +38,6 @@
 from twisted.web2 import responsecode
 from twisted.web2.http import HTTPError, StatusResponse
 
-from twistedcaldav.root import RootResource
 from twistedcaldav.sql import AbstractSQLDatabase
 
 DEBUG_LOG = False
@@ -51,6 +50,7 @@
     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
@@ -58,8 +58,14 @@
                 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):
         """
@@ -100,11 +106,11 @@
             
         return self.index.getSeveralPropertyValues(self.rname, qnames)
 
-    def getAll(self):
+    def getAll(self, nohidden=True):
         """
         Read all properties from index.
         
-        @param qnames: C{list} of C{tuple} of property namespace and name.
+        @param nohidden: C{True} to not return hidden properties, C{False} otherwise.
         @return: a C{dict} containing property name/value.
         """
         if not self.index:
@@ -113,7 +119,7 @@
                 "No properties"
             ))
             
-        return self.index.getAllPropertyValues(self.rname)
+        return self.index.getAllPropertyValues(self.rname, nohidden)
 
     def getSeveralResources(self, qnames):
         """
@@ -125,14 +131,30 @@
         if not qnames:
             return None
 
-        if not self.index:
+        if not self.childindex:
             raise HTTPError(StatusResponse(
                 responsecode.NOT_FOUND,
                 "No such property: {%s}%s" % qnames[0]
             ))
             
-        return self.index.getSeveralResourcePropertyValues(qnames)
+        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 proeprties"
+            ))
+            
+        return self.childindex.getAllResourcePropertyValues(nohidden)
+
     def set(self, property):
         """
         Write property into index.
@@ -141,7 +163,7 @@
         """
 
         if self.index:
-            self.index.setOnePropertyValue(self.rname, property.qname(), property)
+            self.index.setOnePropertyValue(self.rname, property.qname(), property, property.hidden)
 
     def setSeveral(self, properties):
         """
@@ -151,7 +173,7 @@
         """
 
         if self.index:
-            self.index.setSeveralPropertyValues(self.rname, [(p.qname(), p) for p in properties])
+            self.index.setSeveralPropertyValues(self.rname, [(p.qname(), p, p.hidden) for p in properties])
 
     def delete(self, qname):
         """
@@ -209,6 +231,19 @@
         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.
@@ -285,18 +320,19 @@
         path = os.path.join(path, SQLPropertiesDatabase.dbFilename)
         super(SQLPropertiesDatabase, self).__init__(path, SQLPropertiesDatabase.dbFormatVersion, utf8=True)
 
-    def setOnePropertyValue(self, rname, pname, pvalue):
+    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 proeprty, C{False} otehrwise.
         @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())
+        self._add_to_db(rname, self._encode(pname), cPickle.dumps(pvalue), pvalue.toxml(), hidden)
         self._db_commit()
 
     def setSeveralPropertyValues(self, rname, properties):
@@ -311,7 +347,7 @@
         # 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())
+            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):
@@ -350,12 +386,12 @@
         if DEBUG_LOG:
             log.msg("getSeveralPropertyValues: %s \"%s\"" % (self.dbpath, pnames))
         properties = {}
-        statement = "select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1 and ("
+        statement = "select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1 and PROPERTYNAME in ("
         args = [rname]
         for i, pname in enumerate(pnames):
             if i != 0:
-                statement += " or "
-            statement += "PROPERTYNAME=:%s" % (i + 2,)
+                statement += ", "
+            statement += ":%s" % (i + 2,)
             args.append(self._encode(pname))
         statement += ")"
 
@@ -364,19 +400,21 @@
 
         return properties
 
-    def getAllPropertyValues(self, rname):
+    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.
         """
         
-        # Remove what is there, then add it back.
-        if DEBUG_LOG:
-            log.msg("getAllPropertyValues: %s" % (self.dbpath,))
         properties = {}
-        for row in self._db_execute("select PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where RESOURCENAME = :1", rname):
+        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
@@ -393,19 +431,38 @@
         if DEBUG_LOG:
             log.msg("getAllPropertyValues: %s \"%s\"" % (self.dbpath, pnames))
         members = {}
-        statement = "select RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where "
+        statement = "select RESOURCENAME, PROPERTYNAME, PROPERTYOBJECT from PROPERTIES where PROPERTYNAME in ("
         args = []
         for i, pname in enumerate(pnames):
             if i != 0:
-                statement += " or "
-            statement += "PROPERTYNAME=:%s" % (i + 1,)
+                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.
@@ -455,6 +512,19 @@
             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,)
@@ -487,7 +557,7 @@
             results.setdefault(row[0], set()).add(self._decode(row[1]))
         return results
 
-    def _add_to_db(self, rname, pname, pobject, pvalue):
+    def _add_to_db(self, rname, pname, pobject, pvalue, hidden):
         """
         Add a property.
     
@@ -497,11 +567,15 @@
         @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)
-            values (:1, :2, :3, :4)
-            """, rname, pname, pobject, pvalue
+            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):
@@ -547,7 +621,8 @@
                 RESOURCENAME   text,
                 PROPERTYNAME   text,
                 PROPERTYOBJECT text,
-                PROPERTYVALUE  text
+                PROPERTYVALUE  text,
+                HIDDEN         text(1)
             )
             """
         )

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/static.py	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/static.py	2007-02-28 22:06:40 UTC (rev 1295)
@@ -147,6 +147,7 @@
             if status != responsecode.CREATED:
                 raise HTTPError(status)
     
+            self.create()
             self.writeDeadProperty(resourceType)
             return status
         
@@ -412,6 +413,8 @@
             fp.open("w").close()
             fp.restat(False)
 
+        self.create()
+
         return True
 
 class CalendarHomeProvisioningFile (AutoProvisioningFileMixIn, DirectoryCalendarHomeProvisioningResource, DAVFile):

Modified: CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/tap.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/tap.py	2007-02-28 19:26:14 UTC (rev 1294)
+++ CalendarServer/branches/users/cdaboo/sqlprops-1202/twistedcaldav/tap.py	2007-02-28 22:06:40 UTC (rev 1295)
@@ -328,6 +328,10 @@
         root.putChild('principals', principalCollection)
         root.putChild('calendars', calendarCollection)
 
+        root.create()
+        #principalCollection.provision()
+        #calendarCollection.provision()
+
         # Configure default ACLs on the root resource
 
         log.msg("Setting up default ACEs on root resource")

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070228/963f757d/attachment.html


More information about the calendarserver-changes mailing list