[CalendarServer-changes] [283] CalendarServer/trunk/lib-patches/Twisted

source_changes at macosforge.org source_changes at macosforge.org
Fri Oct 20 08:18:51 PDT 2006


Revision: 283
          http://trac.macosforge.org/projects/calendarserver/changeset/283
Author:   cdaboo at apple.com
Date:     2006-10-20 08:18:50 -0700 (Fri, 20 Oct 2006)

Log Message:
-----------
Patch updates. Includes performance tweaks on locateResource etc.

Modified Paths:
--------------
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.proppatch.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch

Added Paths:
-----------
    CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.davxml.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/davxml.py
 ===================================================================
---- twisted/web2/dav/davxml.py	(revision 18219)
+--- twisted/web2/dav/davxml.py	(revision 18375)
 +++ twisted/web2/dav/davxml.py	(working copy)
 @@ -45,6 +45,7 @@
  from twisted.web2.dav.element.rfc2518 import *

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.__init__.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/element/__init__.py
 ===================================================================
---- twisted/web2/dav/element/__init__.py	(revision 18219)
+--- twisted/web2/dav/element/__init__.py	(revision 18375)
 +++ twisted/web2/dav/element/__init__.py	(working copy)
 @@ -35,4 +35,5 @@
      "rfc2518",

Added: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch	                        (rev 0)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.base.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -0,0 +1,125 @@
+Index: twisted/web2/dav/element/base.py
+===================================================================
+--- twisted/web2/dav/element/base.py	(revision 18375)
++++ twisted/web2/dav/element/base.py	(working copy)
+@@ -190,14 +190,93 @@
+         return child in self.children
+ 
+     def writeXML(self, output):
+-        document = xml.dom.minidom.Document()
+-        self.addToDOM(document, None)
+-        PrintXML(document, stream=output)
++        # FIXME: Now have a 'fast' write implementation as well as previous PyXML-based one.
++        # For now the fast one is the default and we will test to see if its good enough.
++        
++        usePyXML = False
++        if usePyXML:
++            document = xml.dom.minidom.Document()
++            self.addToDOM(document, None)
++            PrintXML(document, stream=output)
++        else:
++            output.write("<?xml version='1.0' encoding='UTF-8'?>\r\n")
++            self.writeToStream(output, "", 0, True)
++            output.write("\r\n")
++    
++    def writeToStream(self, output, ns, level, pretty):
++        """
++        Fast XML output.
++
++        @param output: C{stream} to write to.
++        @param ns: C{str} containing the namespace of the enclosing element.
++        @param level: C{int} containing the element nesting level (starts at 0).
++        @param pretty: C{bool} whether to use 'pretty' formatted output or not.
++        """
++        
++        # Do pretty indent
++        if pretty and level:
++            output.write("  " * level)
++        
++        # Check for empty element (one with either no children or a single PCDATA that is itself empty)
++        if (len(self.children) == 0 or
++            (len(self.children) == 1 and isinstance(self.children[0], PCDATAElement) and len(str(self.children[0])) == 0)):
++
++            # Write out any attributes or the namespace if difference from enclosing element.
++            if self.attributes or (ns != self.namespace):
++                output.write("<%s" % (self.name,))
++                for name, value in self.attributes.iteritems():
++                    self.writeAttributeToStream(output, name, value)
++                if ns != self.namespace:
++                    output.write(" xmlns='%s'" % (self.namespace,))
++                output.write("/>")
++            else:
++                output.write("<%s/>" % (self.name,))
++        else:
++            # Write out any attributes or the namespace if difference from enclosing element.
++            if self.attributes or (ns != self.namespace):
++                output.write("<%s" % (self.name,))
++                for name, value in self.attributes.iteritems():
++                    self.writeAttributeToStream(output, name, value)
++                if ns != self.namespace:
++                    output.write(" xmlns='%s'" % (self.namespace,))
++                    ns = self.namespace
++                output.write(">")
++            else:
++                output.write("<%s>" % (self.name,))
++                
++            # Determine nature of children when doing pretty print: we do
++            # not want to insert CRLFs or any other whitespace in PCDATA.
++            hasPCDATA = False
++            for child in self.children:
++                if isinstance(child, PCDATAElement):
++                    hasPCDATA = True
++                    break
++            
++            # Write out the children.
++            if pretty and not hasPCDATA:
++                output.write("\r\n")
++            for child in self.children:
++                child.writeToStream(output, ns, level+1, pretty)
++                
++            # Close the element.
++            if pretty and not hasPCDATA and level:
++                output.write("  " * level)
++            output.write("</%s>" % (self.name,))
++
++        if pretty and level:
++            output.write("\r\n")
+ 
++    def writeAttributeToStream(self, output, name, value):
++        
++        # Quote any single quotes. We do not need to be any smarter than this.
++        value = value.replace("'", "&apos;")
++
++        output.write(" %s='%s'" % (name, value,))  
++      
+     def toxml(self):
+         output = StringIO.StringIO()
+         self.writeXML(output)
+-        return output.getvalue()
++        return str(output.getvalue())
+ 
+     def element(self, document):
+         element = document.createElementNS(self.namespace, self.name)
+@@ -324,6 +403,22 @@
+             log.err("Invalid PCDATA: %r" % (self.data,))
+             raise
+ 
++    def writeToStream(self, output, ns, level, pretty):
++        # Do escaping/CDATA behavior
++        if "\r" in self.data or "\n" in self.data:
++            # Do CDATA
++            cdata = "<![CDATA[%s]]>" % (self.data.replace("]]>", "]]&lt;"),)
++        else:
++            cdata = self.data
++            if "&" in cdata:
++                cdata = cdata.replace("&", "&amp;")
++            if "<" in cdata:
++                cdata = cdata.replace("&", "&lt;")
++            if "]]>" in cdata:
++                cdata = cdata.replace("]]>", "]]&lt;")
++
++        output.write(cdata)
++
+ class WebDAVOneShotElement (WebDAVElement):
+     """
+     Element with exactly one WebDAVEmptyElement child and no attributes.

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.element.rfc4331.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -2,7 +2,7 @@
 ===================================================================
 --- twisted/web2/dav/element/rfc4331.py	(revision 0)
 +++ twisted/web2/dav/element/rfc4331.py	(revision 0)
-@@ -0,0 +1,55 @@
+@@ -0,0 +1,110 @@
 +##
 +# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
 +#
@@ -58,3 +58,58 @@
 +    name = "quota-used-bytes"
 +    hidden = True
 +    protected = True
++##
++# 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: Cyrus Daboo, cdaboo at apple.com
++##
++
++"""
++RFC 4331 (Quota and Size Properties for WebDAV Collections) XML Elements
++
++This module provides XML element definitions for use with WebDAV.
++
++See RFC 4331: http://www.ietf.org/rfc/rfc4331.txt
++"""
++
++from twisted.web2.dav.element.base import WebDAVTextElement
++
++##
++# Section 3 & 4 (Quota Properties)
++##
++
++class QuotaAvailableBytes (WebDAVTextElement):
++    """
++    Property which contains the the number of bytes available under the
++    current quota to store data in a collection (RFC 4331, section 3)
++    """
++    name = "quota-available-bytes"
++    hidden = True
++    protected = True
++
++class QuotaUsedBytes (WebDAVTextElement):
++    """
++    Property which contains the the number of bytes used under the
++    current quota to store data in a collection (RFC 4331, section 4)
++    """
++    name = "quota-used-bytes"
++    hidden = True
++    protected = True

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.idav.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,8 +1,26 @@
 Index: twisted/web2/dav/idav.py
 ===================================================================
---- twisted/web2/dav/idav.py	(revision 18219)
+--- twisted/web2/dav/idav.py	(revision 18375)
 +++ twisted/web2/dav/idav.py	(working copy)
-@@ -180,6 +180,80 @@
+@@ -41,7 +41,7 @@
+             otherwise.
+         """
+ 
+-    def findChildren(depth, request, callback, privileges):
++    def findChildren(depth, request, callback, privileges, inherited_aces):
+         """
+         Returns an iterable of child resources for the given depth.
+         Because resources do not know their request URIs, chidren are returned
+@@ -52,6 +52,8 @@
+         @param callback: C{callable} that will be called for each child found
+         @param privileges: the list of L{Privilege}s to test for.  This should 
+             default to None.
++        @param inherited_aces: a list of L{Privilege}s for aces being inherited from
++            the parent collection used to bypass inheritance lookup.
+         @return: An L{Deferred} that fires when all the children have been found
+         """
+ 
+@@ -180,6 +182,80 @@
              the specified principal.
          """
  

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.__init__.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/__init__.py
 ===================================================================
---- twisted/web2/dav/method/__init__.py	(revision 18219)
+--- twisted/web2/dav/method/__init__.py	(revision 18375)
 +++ twisted/web2/dav/method/__init__.py	(working copy)
 @@ -40,6 +40,7 @@
      "proppatch",

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.delete.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/delete.py
 ===================================================================
---- twisted/web2/dav/method/delete.py	(revision 18219)
+--- twisted/web2/dav/method/delete.py	(revision 18375)
 +++ twisted/web2/dav/method/delete.py	(working copy)
 @@ -58,8 +58,28 @@
      yield x

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.propfind.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -41,7 +41,7 @@
  
              for property in properties_to_enumerate:
 -                if property in resource_properties:
-+                has = waitForDeferred(self.hasProperty(property, request))
++                has = waitForDeferred(resource.hasProperty(property, request))
 +                yield has
 +                has = has.getResult()
 +                if has:

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.proppatch.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.proppatch.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.proppatch.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/proppatch.py
 ===================================================================
---- twisted/web2/dav/method/proppatch.py	(revision 18219)
+--- twisted/web2/dav/method/proppatch.py	(revision 18375)
 +++ twisted/web2/dav/method/proppatch.py	(working copy)
 @@ -105,7 +105,7 @@
                  if has:

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.method.put.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/dav/method/put.py
 ===================================================================
---- twisted/web2/dav/method/put.py	(revision 18219)
+--- twisted/web2/dav/method/put.py	(revision 18375)
 +++ twisted/web2/dav/method/put.py	(working copy)
 @@ -34,7 +34,7 @@
  from twisted.web2 import responsecode

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.resource.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -176,7 +176,15 @@
  
          for resource, uri in resources:
              acl = waitForDeferred(resource.accessControlList(request, inherited_aces=inherited_aces))
-@@ -1501,6 +1589,265 @@
+@@ -1004,7 +1092,6 @@
+             url = request.urlForResource(self)
+ 
+             assert url is not None, "urlForResource(self) returned None for resource %s" % (self,)
+-
+             return url
+ 
+         try:
+@@ -1501,6 +1588,265 @@
          return None
  
      ##
@@ -442,7 +450,7 @@
      # HTTP
      ##
  
-@@ -1548,7 +1895,7 @@
+@@ -1548,7 +1894,7 @@
      """
      DAV resource with no children.
      """
@@ -451,7 +459,7 @@
          return succeed(None)
  
  class DAVPrincipalResource (DAVLeafResource):
-@@ -1574,7 +1921,7 @@
+@@ -1574,7 +1920,7 @@
      def isCollection(self):
          return False
  
@@ -460,7 +468,7 @@
          return succeed(None)
  
      def readProperty(self, property, request):
-@@ -1702,6 +2049,37 @@
+@@ -1702,6 +2048,37 @@
  davxml.registerElement(TwistedACLInheritable)
  davxml.ACE.allowed_children[(twisted_dav_namespace, "inheritable")] = (0, 1)
  

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.dav.static.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -88,8 +88,16 @@
      # Workarounds for issues with File
      ##
  
-@@ -135,8 +185,12 @@
+@@ -133,11 +183,22 @@
+                     # Render from the index file
+                     standin = self.createSimilarFile(ifp.path)
                  else:
++                    # Do some optimisation of access control calculation by determining any inherited ACLs outside of
++                    # the child resource loop and supply those to the checkPrivileges on each child.
++                    filteredaces = waitForDeferred(self.inheritedACEsforChildren(request))
++                    yield filteredaces
++                    filteredaces = filteredaces.getResult()
++
                      children = []
  
 +                    def _childname(r, u):
@@ -98,7 +106,10 @@
 +
                      d = self.findChildren('1', request,
 -                                          lambda r,u: children.append(os.path.basename(u)),
+-                                          (davxml.Read(),))
 +                                          _childname,
-                                           (davxml.Read(),))
++                                          (davxml.Read(),),
++                                          inherited_aces=filteredaces)
                      d = waitForDeferred(d)
                      yield d
+                     d = d.getResult()

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.log.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -1,6 +1,6 @@
 Index: twisted/web2/log.py
 ===================================================================
---- twisted/web2/log.py	(revision 18219)
+--- twisted/web2/log.py	(revision 18375)
 +++ twisted/web2/log.py	(working copy)
 @@ -88,7 +88,7 @@
  class LogWrapperResource(resource.WrapperResource):

Modified: CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch
===================================================================
--- CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch	2006-10-20 15:17:42 UTC (rev 282)
+++ CalendarServer/trunk/lib-patches/Twisted/twisted.web2.server.patch	2006-10-20 15:18:50 UTC (rev 283)
@@ -11,7 +11,17 @@
  
  
  """This is a web-sever which integrates with the twisted.internet
-@@ -156,7 +158,9 @@
+@@ -150,6 +152,9 @@
+             self._initialprepath = kw['prepathuri']
+             del kw['prepathuri']
+ 
++        self._resourcesByURL = {}
++        self._resourcesFromURL = {}
++
+         # Copy response filters from the class
+         self.responseFilters = self.responseFilters[:]
+         self.files = {}
+@@ -156,7 +161,9 @@
          self.resources = []
          http.Request.__init__(self, *args, **kw)
  
@@ -22,15 +32,46 @@
          if atEnd:
              self.responseFilters.append(f)
          else:
-@@ -347,6 +351,7 @@
+@@ -263,8 +270,15 @@
+             failedDeferred = self._processingFailed(failure.Failure())
+             return
+         
++        def _registerResource(child):
++            url = "/" + "/".join(self.prepath)
++            self._resourcesByURL[child] = url
++            self._resourcesFromURL[url] = child
++            return child
++        
+         d = defer.Deferred()
+         d.addCallback(self._getChild, self.site.resource, self.postpath)
++        d.addCallback(_registerResource)
+         d.addCallback(lambda res, req: res.renderHTTP(req), self)
+         d.addCallback(self._cbFinishRender)
+         d.addErrback(self._processingFailed)
+@@ -321,7 +335,7 @@
+                 else:
+                     url = "/"
+         
+-                self._rememberURLForResource(quote(url), res)
++                #self._rememberURLForResource(quote(url), res)
+                 return res
+             #else:
+             #    raise ValueError("locateChild must not return StopTraversal with a resource other than self.")
+@@ -342,12 +356,10 @@
+                 self.prepath.append(self.postpath.pop(0))
+ 
+         child = self._getChild(None, newres, newpath, updatepaths=updatepaths)
+-        self._rememberURLForResource(quote(url), child)
++        #self._rememberURLForResource(quote(url), child)
+ 
          return child
  
-     _resourcesByURL = weakref.WeakKeyDictionary()
-+    _resourcesFromURL = weakref.WeakValueDictionary()
- 
+-    _resourcesByURL = weakref.WeakKeyDictionary()
+-
      def _rememberURLForResource(self, url, resource):
          """
-@@ -387,6 +392,11 @@
+         Remember the URL of visited resources.
+@@ -387,6 +399,11 @@
          """
          if url is None: return None
  
@@ -42,13 +83,14 @@
          #
          # Parse the URL
          #
-@@ -417,7 +427,50 @@
+@@ -417,7 +434,52 @@
                  raise f
              return None
  
 -        return defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
 +        def _registerResource(child):
-+            self._resourcesFromURL[url] = child
++            self._resourcesByURL[child] = path
++            self._resourcesFromURL[path] = child
 +            return child
 +        
 +        d = defer.maybeDeferred(self._getChild, None, self.site.resource, segments, updatepaths=False)
@@ -85,6 +127,7 @@
 +            return None
 +
 +        def _registerResource(child):
++            self._resourcesByURL[child] = url
 +            self._resourcesFromURL[url] = child
 +            return child
 +        

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


More information about the calendarserver-changes mailing list