[CalendarServer-changes] [5806] CalendarServer/branches/new-store

source_changes at macosforge.org source_changes at macosforge.org
Tue Jun 22 16:38:47 PDT 2010


Revision: 5806
          http://trac.macosforge.org/projects/calendarserver/changeset/5806
Author:   glyph at apple.com
Date:     2010-06-22 16:38:46 -0700 (Tue, 22 Jun 2010)
Log Message:
-----------
Merge from trunk again, adjusting slightly for errors introduced by doing so.

Modified Paths:
--------------
    CalendarServer/branches/new-store/calendarserver/tap/caldav.py
    CalendarServer/branches/new-store/conf/caldavd-test.plist
    CalendarServer/branches/new-store/conf/carddavd-test.plist
    CalendarServer/branches/new-store/testserver
    CalendarServer/branches/new-store/twext/web2/dav/method/delete.py
    CalendarServer/branches/new-store/twext/web2/dav/resource.py
    CalendarServer/branches/new-store/twistedcaldav/customxml.py
    CalendarServer/branches/new-store/twistedcaldav/datafilters/peruserdata.py
    CalendarServer/branches/new-store/twistedcaldav/datafilters/test/test_peruserdata.py
    CalendarServer/branches/new-store/twistedcaldav/directory/principal.py
    CalendarServer/branches/new-store/twistedcaldav/mail.py
    CalendarServer/branches/new-store/twistedcaldav/method/put_common.py
    CalendarServer/branches/new-store/twistedcaldav/resource.py
    CalendarServer/branches/new-store/twistedcaldav/scheduling/utils.py
    CalendarServer/branches/new-store/twistedcaldav/sharing.py
    CalendarServer/branches/new-store/twistedcaldav/static.py
    CalendarServer/branches/new-store/twistedcaldav/stdconfig.py
    CalendarServer/branches/new-store/twistedcaldav/storebridge.py
    CalendarServer/branches/new-store/twistedcaldav/test/data/calendar.10.tgz
    CalendarServer/branches/new-store/twistedcaldav/test/data/calendar.100.tgz
    CalendarServer/branches/new-store/twistedcaldav/test/data/calendar.1000.tgz
    CalendarServer/branches/new-store/twistedcaldav/test/data/makelargecalendars.py
    CalendarServer/branches/new-store/twistedcaldav/test/test_sharing.py

Added Paths:
-----------
    CalendarServer/branches/new-store/twext/web2/dav/method/delete_common.py

Property Changed:
----------------
    CalendarServer/branches/new-store/


Property changes on: CalendarServer/branches/new-store
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:5594-5718
   + /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:5594-5803

Modified: CalendarServer/branches/new-store/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/new-store/calendarserver/tap/caldav.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/calendarserver/tap/caldav.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -1354,6 +1354,7 @@
             log.error("Could not get passphrase for %s: %s"
                       % (config.SSLPrivateKey, error))
         else:
+            log.info("Obtained passphrase for %s" % (config.SSLPrivateKey))
             return output.strip()
 
     if (

Modified: CalendarServer/branches/new-store/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/new-store/conf/caldavd-test.plist	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/conf/caldavd-test.plist	2010-06-22 23:38:46 UTC (rev 5806)
@@ -686,8 +686,6 @@
       <dict>
     	<key>Enabled</key>
     	<true/>
-    	<key>AllowScheduling</key>
-    	<false/>
       </dict>
       <key>AddressBooks</key>
       <dict>

Modified: CalendarServer/branches/new-store/conf/carddavd-test.plist
===================================================================
--- CalendarServer/branches/new-store/conf/carddavd-test.plist	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/conf/carddavd-test.plist	2010-06-22 23:38:46 UTC (rev 5806)
@@ -704,8 +704,6 @@
       <dict>
     	<key>Enabled</key>
     	<false/>
-    	<key>AllowScheduling</key>
-    	<false/>
       </dict>
       <key>AddressBooks</key>
       <dict>

Modified: CalendarServer/branches/new-store/testserver
===================================================================
--- CalendarServer/branches/new-store/testserver	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/testserver	2010-06-22 23:38:46 UTC (rev 5806)
@@ -63,10 +63,10 @@
 
 export PYTHONPATH=$("${wd}/run" -p);
 
-if [ ! -e "${documentroot}/calendars/__uids__/user01" ]; then
-  curl "http://localhost:8008/calendars/__uids__/user01/";
+if [ ! -e "${documentroot}/calendars/__uids__/user09" ]; then
+  curl "http://localhost:8008/calendars/__uids__/user09/";
 fi;
 
-python twistedcaldav/test/data/makelargecalendars.py -n 1 -d "${documentroot}";
+python twistedcaldav/test/data/makelargecalendars.py -o 9 -d "${documentroot}";
 
 cd "${cdt}" && python testcaldav.py -s "${serverinfo}" "$@";

Modified: CalendarServer/branches/new-store/twext/web2/dav/method/delete.py
===================================================================
--- CalendarServer/branches/new-store/twext/web2/dav/method/delete.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twext/web2/dav/method/delete.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -35,7 +35,7 @@
 from twext.web2 import responsecode
 from twext.web2.http import HTTPError
 from twext.web2.dav import davxml
-from twext.web2.dav.fileop import delete
+from twext.web2.dav.method.delete_common import deleteResource
 from twext.web2.dav.util import parentForURL
 
 log = Logger()
@@ -62,28 +62,8 @@
     yield x
     x.getResult()
 
-    # Do quota checks before we start deleting things
-    myquota = waitForDeferred(self.quota(request))
-    yield myquota
-    myquota = myquota.getResult()
-    if myquota is not None:
-        old_size = waitForDeferred(self.quotaSize(request))
-        yield old_size
-        old_size = old_size.getResult()
-    else:
-        old_size = 0
-
-    # Do delete
-    x = waitForDeferred(delete(request.uri, self.fp, depth))
+    x = waitForDeferred(deleteResource(request, self, request.uri, depth))
     yield x
-    result = x.getResult()
+    yield x.getResult()
 
-    # Adjust quota
-    if myquota is not None:
-        d = waitForDeferred(self.quotaSizeAdjust(request, -old_size))
-        yield d
-        d.getResult()
-    
-    yield result
-
 http_DELETE = deferredGenerator(http_DELETE)

Copied: CalendarServer/branches/new-store/twext/web2/dav/method/delete_common.py (from rev 5803, CalendarServer/trunk/twext/web2/dav/method/delete_common.py)
===================================================================
--- CalendarServer/branches/new-store/twext/web2/dav/method/delete_common.py	                        (rev 0)
+++ CalendarServer/branches/new-store/twext/web2/dav/method/delete_common.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -0,0 +1,72 @@
+# -*- test-case-name: twext.web2.dav.test.test_delete -*-
+##
+# Copyright (c) 2005-2010 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.
+##
+
+"""
+WebDAV DELETE method
+"""
+
+__all__ = ["deleteResource"]
+
+from twisted.internet.defer import waitForDeferred, deferredGenerator
+
+from twext.python.log import Logger
+from twext.web2 import responsecode
+from twext.web2.http import HTTPError
+from twext.web2.dav.fileop import delete
+
+log = Logger()
+
+
+def deleteResource(request, resource, resource_uri, depth="0"):
+    """
+    Handle a resource delete with proper quota etc updates
+    """
+    if not resource.fp.exists():
+        log.err("File not found: %s" % (resource.fp.path,))
+        raise HTTPError(responsecode.NOT_FOUND)
+
+    # Do quota checks before we start deleting things
+    myquota = waitForDeferred(resource.quota(request))
+    yield myquota
+    myquota = myquota.getResult()
+    if myquota is not None:
+        old_size = waitForDeferred(resource.quotaSize(request))
+        yield old_size
+        old_size = old_size.getResult()
+    else:
+        old_size = 0
+
+    # Do delete
+    x = waitForDeferred(delete(resource_uri, resource.fp, depth))
+    yield x
+    result = x.getResult()
+
+    # Adjust quota
+    if myquota is not None:
+        d = waitForDeferred(resource.quotaSizeAdjust(request, -old_size))
+        yield d
+        d.getResult()
+    
+    yield result
+
+deleteResource = deferredGenerator(deleteResource)

Modified: CalendarServer/branches/new-store/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/branches/new-store/twext/web2/dav/resource.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twext/web2/dav/resource.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -145,7 +145,7 @@
            #(dav_namespace, "group"                     ), # RFC 3744, section 5.2
             (dav_namespace, "supported-privilege-set"   ), # RFC 3744, section 5.3
             (dav_namespace, "current-user-privilege-set"), # RFC 3744, section 5.4
-            (dav_namespace, "current-user-principal"    ), # draft-sanchez-webdav-current-principal
+            (dav_namespace, "current-user-principal"    ), # RFC 5397, Section 3
             (dav_namespace, "acl"                       ), # RFC 3744, section 5.5
             (dav_namespace, "acl-restrictions"          ), # RFC 3744, section 5.6
             (dav_namespace, "inherited-acl-set"         ), # RFC 3744, section 5.7
@@ -171,7 +171,7 @@
 
         @return: a dict-like object from which one can read and to
             which one can write dead properties.  Keys are qname
-            tuples (ie. C{(namespace, name)}) as returned by
+            tuples (i.e. C{(namespace, name)}) as returned by
             L{davxml.WebDAVElement.qname()} and values are
             L{davxml.WebDAVElement} instances.
         """
@@ -220,7 +220,7 @@
 
             if namespace == dav_namespace:
                 if name == "resourcetype":
-                    # Allow live property to be overriden by dead property
+                    # Allow live property to be overridden by dead property
                     if self.deadProperties().contains(qname):
                         return self.deadProperties().get(qname)
                     if self.isCollection():
@@ -1055,7 +1055,7 @@
             access control list for this resource.
         """
         #
-        # The default behaviour is no ACL; we should inherrit from the parent
+        # The default behaviour is no ACL; we should inherit from the parent
         # collection.
         #
         return davxml.ACL()
@@ -1274,7 +1274,7 @@
             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
+            to the pre-computed inheritable aces from the parent
             resource hierarchy.
         @return: a L{Deferred} that callbacks with C{None} or errbacks
             with an L{AccessDeniedError}
@@ -1533,8 +1533,8 @@
 
     def principalsForAuthID(self, request, authid):
         """
-        Return authentication and authorization prinicipal identifiers
-        for the authentication identifer passed in. In this
+        Return authentication and authorization principal identifiers
+        for the authentication identifier passed in. In this
         implementation authn and authz principals are the same.
 
         @param request: the L{IRequest} for the request in progress.
@@ -1562,7 +1562,7 @@
     def findPrincipalForAuthID(self, authid):
         """
         Return authentication and authorization principal identifiers
-        for the authentication identifer passed in. In this
+        for the authentication identifier passed in. In this
         implementation authn and authz principals are the same.
 
         @param authid: a string containing the
@@ -1583,7 +1583,7 @@
         """
         Determine the authorization principal for the given request
         and authentication principal.  This implementation simply uses
-        aht authentication principalk as the authoization principal.
+        that authentication principal as the authorization principal.
         
         @param request: the L{IRequest} for the request in progress.
         @param authid: a string containing the
@@ -1599,7 +1599,7 @@
         
     def samePrincipal(self, principal1, principal2):
         """
-        Check whether the two prinicpals are exactly the same in terms of
+        Check whether the two principals are exactly the same in terms of
         elements and data.
 
         @param principal1: a L{Principal} to test.
@@ -1965,7 +1965,7 @@
     queried for quota information. If not found, no quota exists for
     the resource.
     
-    To determine tha actual quota in use we will cache the used byte
+    To determine that actual quota in use we will cache the used byte
     count on the quota-root collection in another private property. It
     is the servers responsibility to keep that property up to date by
     adjusting it after every PUT, DELETE, COPY, MOVE, MKCOL,
@@ -1980,7 +1980,7 @@
         Get current available & used quota values for this resource's
         quota root collection.
 
-        @return: an L{Defered} with result C{tuple} containing two
+        @return: an L{Deferred} with result C{tuple} containing two
             C{int}'s the first is quota-available-bytes, the second is
             quota-used-bytes, or C{None} if quota is not defined on
             the resource.
@@ -1993,60 +1993,45 @@
         else:
             request.quota = {}
 
-        # Check this resource first
-        if self.isCollection():
-            qroot = self.quotaRoot(request)
-            if qroot is not None:
-                def gotUsage(used):
-                    available = qroot - used
-                    if available < 0:
-                        available = 0
-                    request.quota[self] = (available, used)
-                    return (available, used)
+        # Find the quota root for this resource and return its data
+        def gotQuotaRootResource(qroot_resource):
+            if qroot_resource:
+                qroot = qroot_resource.quotaRoot(request)
+                if qroot is not None:
+                    def gotUsage(used):
+                        available = qroot - used
+                        if available < 0:
+                            available = 0
+                        request.quota[self] = (available, used)
+                        return (available, used)
+    
+                    d = qroot_resource.currentQuotaUse(request)
+                    d.addCallback(gotUsage)
+                    return d
 
-                d = self.currentQuotaUse(request)
-                d.addCallback(gotUsage)
-                return d
-        
-        # Check the next parent
-        url = request.urlForResource(self)
-        if url != "/":
-            def gotQuota(quota):
-                request.quota[self] = quota
-                return quota
-
-            d = request.locateResource(parentForURL(url))
-            d.addCallback(lambda p: p.quota(request))
-            d.addCallback(gotQuota)
-            return d
-        else:
             request.quota[self] = None
+            return None
 
-        return succeed(request.quota[self])
+            
+        d = self.quotaRootResource(request)
+        d.addCallback(gotQuotaRootResource)
+        return d
     
     def hasQuota(self, request):
         """
-        Check whether this resource is undre quota control by checking
+        Check whether this resource is under quota control by checking
         each parent to see if it has a quota root.
         
         @return: C{True} if under quota control, C{False} if not.
         """
+
+        def gotQuotaRootResource(qroot_resource):
+            
+            return qroot_resource is not None
         
-        # Check this one first
-        if self.hasQuotaRoot(request):
-            return succeed(True)
-        
-        # Look at each parent
-        try:
-            url = request.urlForResource(self)
-            if url != "/":
-                d = request.locateResource(parentForURL(url))
-                d.addCallback(lambda p: p.hasQuota(request))
-                return d
-            else:
-                return succeed(False)
-        except NoURLForResourceError:
-            return succeed(False)
+        d = self.quotaRootResource(request)
+        d.addCallback(gotQuotaRootResource)
+        return d
 
     def hasQuotaRoot(self, request):
         """
@@ -2066,15 +2051,21 @@
             return None
     
     @inlineCallbacks
-    def quotaRootParent(self, request):
+    def quotaRootResource(self, request):
         """
-        Return the next quota root above this resource.
+        Return the quota root for this resource.
         
         @return: L{DAVResource} or C{None}
         """
 
+        if self.hasQuotaRoot(request):
+            returnValue(self)
+
         # Check the next parent
-        url = request.urlForResource(self)
+        try:
+            url = request.urlForResource(self)
+        except NoURLForResourceError:
+            returnValue(None)
         while (url != "/"):
             url = parentForURL(url)
             parent = (yield request.locateResource(url))
@@ -2082,11 +2073,11 @@
                 returnValue(parent)
 
         returnValue(None)
-        
+
     def setQuotaRoot(self, request, maxsize):
         """
         @param maxsize: a C{int} containing the maximum allowed bytes
-            for the contents of this collection, or C{None} tp remove
+            for the contents of this collection, or C{None} to remove
             quota restriction.
         """
         assert self.isCollection(), "Only collections can have a quota root"
@@ -2111,7 +2102,6 @@
         """
         unimplemented(self)
 
-    @inlineCallbacks
     def checkQuota(self, request, available):
         """
         Check to see whether all quota roots have sufficient available
@@ -2125,19 +2115,20 @@
             quota roots, C{False} otherwise.
         """
         
-        quotaroot = self
-        while(quotaroot is not None):
-            # Check quota on this root (if it has one)
-            quota = quotaroot.quotaRoot(request)
-            if quota is not None:
-                if available > quota[0]:
-                    returnValue(False)
+        def _defer(quotaroot):
+            if quotaroot:
+                # Check quota on this root (if it has one)
+                quota = quotaroot.quotaRoot(request)
+                if quota is not None:
+                    if available > quota[0]:
+                        return False
+    
+            return True
 
-            # Check the next parent with a quota root
-            quotaroot = (yield quotaroot.quotaRootParent(request))
+        d = self.quotaRootResource(request)
+        d.addCallback(_defer)
+        return d
 
-        returnValue(True)
-
     def quotaSizeAdjust(self, request, adjust):
         """
         Update the quota used value on all quota root parents of this
@@ -2148,19 +2139,15 @@
             adjust the cached total.
         """
         
-        # Check this resource first
-        if self.isCollection():
-            if self.hasQuotaRoot(request):
-                return self.updateQuotaUse(request, adjust)
         
-        # Check the next parent
-        url = request.urlForResource(self)
-        if url != "/":
-            d = request.locateResource(parentForURL(url))
-            d.addCallback(lambda p: p.quotaSizeAdjust(request, adjust))
-            return d
+        def _defer(quotaroot):
+            if quotaroot:
+                # Check quota on this root (if it has one)
+                return quotaroot.updateQuotaUse(request, adjust)
 
-        return succeed(None)
+        d = self.quotaRootResource(request)
+        d.addCallback(_defer)
+        return d
 
     def currentQuotaUse(self, request):
         """

Modified: CalendarServer/branches/new-store/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/customxml.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/customxml.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -37,6 +37,7 @@
 import datetime
 
 calendarserver_namespace = "http://calendarserver.org/ns/"
+mobileme_namespace = "http://cal.me.com/_namespace/"
 
 calendarserver_proxy_compliance = (
     "calendar-proxy",
@@ -298,7 +299,7 @@
     }
     allowed_children = {
         (calendarserver_namespace, "subscription-url") : (1, 1),
-        (calendarserver_namespace, "apsbundleid") : (1, 1),
+        (mobileme_namespace, "apsbundleid") : (1, 1),
         (calendarserver_namespace, "xmpp-server") : (1, 1),
         (calendarserver_namespace, "xmpp-uri") : (1, 1),
     }
@@ -311,7 +312,7 @@
     allowed_children = { (davxml.dav_namespace, "href"): (0, 1) }
 
 class PubSubAPSBundleIDProperty (davxml.WebDAVTextElement):
-    namespace = calendarserver_namespace
+    namespace = mobileme_namespace
     name = "apsbundleid"
     protected = True
     hidden = True

Modified: CalendarServer/branches/new-store/twistedcaldav/datafilters/peruserdata.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/datafilters/peruserdata.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/datafilters/peruserdata.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -124,6 +124,11 @@
 
         # Make sure input is valid
         icalnew = self.validCalendar(icalnew)
+        
+        # There cannot be any X-CALENDARSERVER-PERUSER components in the new data
+        for component in tuple(icalnew.subcomponents()):
+            if component.name() == PerUserDataFilter.PERUSER_COMPONENT:
+                raise ValueError("Cannot merge calendar data with X-CALENDARSERVER-PERUSER components in it")
 
         # First split the new data into common and per-user pieces
         self._splitPerUserData(icalnew)

Modified: CalendarServer/branches/new-store/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/datafilters/test/test_peruserdata.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/datafilters/test/test_peruserdata.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -976,6 +976,35 @@
         for item in (data, Component.fromString(data),):
             self.assertEqual(str(PerUserDataFilter("").merge(item, None)), result02)
 
+    def test_prevent_injection(self):
+        
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n")
+        
+        for item in (data, Component.fromString(data),):
+            filter = PerUserDataFilter("user01")
+            self.assertRaises(ValueError, filter.merge, item, None)
+        for item in (data, Component.fromString(data),):
+            filter = PerUserDataFilter("")
+            self.assertRaises(ValueError, filter.merge, item, None)
+
 class PerUserDataMergeTestNewRecurring (twistedcaldav.test.util.TestCase):
 
     def test_public_noperuser(self):

Modified: CalendarServer/branches/new-store/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/directory/principal.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/directory/principal.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -518,6 +518,7 @@
             (calendarserver_namespace, "first-name"       ),
             (calendarserver_namespace, "last-name"        ),
             (calendarserver_namespace, "email-address-set"),
+            davxml.ResourceID.qname(),
         )
 
     def __init__(self, parent, record):
@@ -557,7 +558,9 @@
 
         namespace, name = qname
 
-        if namespace == calendarserver_namespace:
+        if qname == davxml.ResourceID.qname():
+            returnValue(davxml.ResourceID(davxml.HRef.fromString("urn:uuid:%s" % (self.record.guid,))))
+        elif namespace == calendarserver_namespace:
             if name == "first-name":
                 firstName = self.record.firstName
                 if firstName:

Modified: CalendarServer/branches/new-store/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/mail.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/mail.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -708,6 +708,17 @@
             self.log_error("Mail gateway couldn't parse To: address (%s) in message %s" % (msg['To'], msg['Message-ID']))
             return
 
+        result = self.db.lookupByToken(token)
+        if result is None:
+            # This isn't a token we recognize
+            self.log_error("Mail gateway found a token (%s) but didn't recognize it in message %s" % (token, msg['Message-ID']))
+            return
+
+        organizer, attendee, icaluid = result
+        organizer = str(organizer)
+        attendee = str(attendee)
+        icaluid = str(icaluid)
+
         for part in msg.walk():
             if part.get_content_type() == "text/calendar":
                 calBody = part.get_payload(decode=True)
@@ -715,23 +726,46 @@
         else:
             # No icalendar attachment
             self.log_error("Mail gateway didn't find an icalendar attachment in message %s" % (msg['Message-ID'],))
-            return
 
+            if not organizer.startswith("mailto:"):
+                self.log_error("Don't have an email address for the organizer; ignoring reply.")
+                return
+
+            # Forward this email to organizer
+            toAddr = organizer[7:]
+            fromAddr = attendee[7:]
+
+            settings = config.Scheduling["iMIP"]["Sending"]
+            if settings["UseSSL"]:
+                contextFactory = ssl.ClientContextFactory()
+            else:
+                contextFactory = None
+
+            deferred = defer.Deferred()
+            del msg["From"]
+            msg["From"] = fromAddr
+            del msg["Reply-To"]
+            msg["Reply-To"] = fromAddr
+            del msg["To"]
+            msg["To"] = toAddr
+            factory = ESMTPSenderFactory(
+                settings["Username"], settings["Password"],
+                fromAddr, toAddr, StringIO(str(msg)), deferred,
+                contextFactory=contextFactory,
+                requireAuthentication=False,
+                requireTransportSecurity=settings["UseSSL"],
+            )
+
+            reactor.connectTCP(settings["Server"], settings["Port"], factory)
+            return deferred
+
+
+        # Process the imip attachment; inject to calendar server
+
         self.log_debug(calBody)
         calendar = ical.Component.fromString(calBody)
         event = calendar.mainComponent()
 
-        # process mail messages from POP or IMAP, inject to calendar server
-        result = self.db.lookupByToken(token)
-        if result is None:
-            # This isn't a token we recognize
-            self.log_error("Mail gateway found a token (%s) but didn't recognize it in message %s" % (token, msg['Message-ID']))
-            return
-
-        organizer, attendee, icaluid = result
-        organizer = str(organizer)
-        attendee = str(attendee)
-        icaluid = str(icaluid)
         calendar.removeAllButOneAttendee(attendee)
         organizerProperty = calendar.getOrganizerProperty()
         if organizerProperty is None:

Modified: CalendarServer/branches/new-store/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/method/put_common.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/method/put_common.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -672,6 +672,16 @@
                 ))
             
             if do_implicit_action and self.allowImplicitSchedule:
+
+                # Cannot do implicit in sharee's shared calendar
+                isvirt = (yield self.destinationparent.isVirtualShare(self.request))
+                if isvirt:
+                    raise HTTPError(ErrorResponse(
+                        responsecode.FORBIDDEN,
+                        (calendarserver_namespace, "sharee-privilege-needed",),
+                        description="Sharee's cannot schedule"
+                    ))
+
                 new_calendar = (yield scheduler.doImplicitScheduling(self.schedule_tag_match))
                 if new_calendar:
                     if isinstance(new_calendar, int):
@@ -698,7 +708,12 @@
             # and we should not change it. This is not ideal as we may duplicate it unnecessarily
             # but we currently have no api to let the caller tell us whether it cares about the
             # whether the calendar data is changed or not.
-            self.calendar = PerUserDataFilter(accessUID).merge(self.calendar.duplicate(), oldCal)
+            try:
+                self.calendar = PerUserDataFilter(accessUID).merge(self.calendar.duplicate(), oldCal)
+            except ValueError:
+                msg = "Invalid per-user data merge"
+                log.err(msg)
+                raise HTTPError(ErrorResponse(responsecode.FORBIDDEN, (caldav_namespace, "valid-calendar-data"), description=msg))
             self.calendardata = None
             
     @inlineCallbacks

Modified: CalendarServer/branches/new-store/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/resource.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/resource.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -1215,6 +1215,34 @@
         """
         return None 
 
+    @inlineCallbacks
+    def quotaRootResource(self, request):
+        """
+        Return the quota root for this resource.
+        
+        @return: L{DAVResource} or C{None}
+        """
+
+        sharedParent = None
+        isvirt = (yield self.isVirtualShare(request))
+        if isvirt:
+            # A virtual share's quota root is the resource owner's root
+            sharedParent = (yield request.locateResource(parentForURL(self._share.hosturl)))
+        else:
+            parent = (yield self.locateParent(request, request.urlForResource(self)))
+            if isCalendarCollectionResource(parent) or isAddressBookCollectionResource(parent):
+                isvirt = (yield parent.isVirtualShare(request))
+                if isvirt:
+                    # A virtual share's quota root is the resource owner's root
+                    sharedParent = (yield request.locateResource(parentForURL(parent._share.hosturl)))
+
+        if sharedParent:
+            result = (yield sharedParent.quotaRootResource(request))
+        else:
+            result = (yield super(CalDAVResource, self).quotaRootResource(request))
+
+        returnValue(result)
+
 class CalendarPrincipalCollectionResource (DAVPrincipalCollectionResource, CalDAVResource):
     """
     CalDAV principal collection.

Modified: CalendarServer/branches/new-store/twistedcaldav/scheduling/utils.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/scheduling/utils.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/scheduling/utils.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -14,12 +14,12 @@
 # limitations under the License.
 ##
 
-from twisted.internet.defer import inlineCallbacks, succeed, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue
 from twistedcaldav.method import report_common
 from twext.web2.dav.util import joinURL
 
 @inlineCallbacks
-def getCalendarObjectForPrincipals(request, principal, uid):
+def getCalendarObjectForPrincipals(request, principal, uid, allow_shared=False):
     """
     Get a copy of the event for a principal.
     """
@@ -40,8 +40,13 @@
         request._rememberResource(calendar_home, calendar_home.url())
 
         # Run a UID query against the UID
+        @inlineCallbacks
+        def queryCalendarCollection(collection, uri):
+            if not allow_shared:
+                isvirt = (yield collection.isVirtualShare(request))
+                if isvirt:
+                    returnValue(True)
 
-        def queryCalendarCollection(collection, uri):
             rname = collection.index().resourceNameForUID(uid)
             if rname:
                 resource = collection.getChild(rname)
@@ -51,9 +56,9 @@
                 result["resource_name"] = rname
                 result["calendar_collection"] = collection
                 result["calendar_collection_uri"] = uri
-                return succeed(False)
+                returnValue(False)
             else:
-                return succeed(True)
+                returnValue(True)
         
         # NB We are by-passing privilege checking here. That should be OK as the data found is not
         # exposed to the user.

Modified: CalendarServer/branches/new-store/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/sharing.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/sharing.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -70,11 +70,6 @@
     def upgradeToShare(self, request):
         """ Upgrade this collection to a shared state """
         
-        # For calendars we only allow upgrades is shared-scheduling is on
-        if request.method not in ("MKCALENDAR", "MKCOL") and self.isCalendarCollection() and \
-            not config.Sharing.Calendars.AllowScheduling and len(self.listChildren()) != 0:
-            raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Cannot upgrade to shared calendar"))
-
         # Change resourcetype
         rtype = (yield self.resourceType(request))
         rtype = davxml.ResourceType(*(rtype.children + (customxml.SharedOwner(),)))
@@ -95,8 +90,8 @@
         self.writeDeadProperty(rtype)
         
         # Remove all invitees
-        records = self.invitesDB().allRecords()
-        yield self.uninviteUserToShare([record.userid for record in records], None, request)
+        for record in self.invitesDB().allRecords():
+            yield self.uninviteRecordFromShare(record, request)
 
         # Remove invites database
         self.invitesDB().remove()
@@ -111,7 +106,7 @@
         return succeed(True)
 
     @inlineCallbacks
-    def changeUserInviteState(self, request, inviteUID, userid, state, summary=None):
+    def changeUserInviteState(self, request, inviteUID, principalURL, state, summary=None):
         
         shared = (yield self.isShared(request))
         if not shared:
@@ -122,7 +117,7 @@
             ))
             
         record = self.invitesDB().recordForInviteUID(inviteUID)
-        if record is None or record.userid != userid:
+        if record is None or record.principalURL != principalURL:
             raise HTTPError(ErrorResponse(
                 responsecode.FORBIDDEN,
                 (customxml.calendarserver_namespace, "valid-request"),
@@ -369,14 +364,14 @@
         # First try to resolve as a principal
         principal = self.principalForCalendarUserAddress(userid)
         if principal:
-            return principal.principalURL(), principal.displayName()
+            return userid, principal.principalURL(), principal.displayName()
         
         # TODO: we do not support external users right now so this is being hard-coded
         # off in spite of the config option.
         #elif config.Sharing.AllowExternalUsers:
-        #    return userid, cn
+        #    return userid, None, cn
         else:
-            return None, None
+            return None, None, None
 
     def validateInvites(self):
         """
@@ -395,19 +390,23 @@
             @param ace: Must be one of customxml.ReadWriteAccess or customxml.ReadAccess
         """
         
-        # Check for valid userid first
-        userid, cn = self.validUserIDWithCommonNameForShare(userid, cn)
-        if userid is None:
-            return succeed(False)
-
         # TODO: Check if this collection is shared, and error out if it isn't
+        resultIsList = True
         if type(userid) is not list:
             userid = [userid]
+            resultIsList = False
         if type(cn) is not list:
             cn = [cn]
             
+        def _defer(resultset):
+            results = [result if success else False for success, result in resultset]
+            if resultIsList:
+                return results
+            else:
+                return results[0]
+
         dl = [self.inviteSingleUserToShare(user, cn, ace, summary, request) for user, cn in zip(userid, cn)]
-        return DeferredList(dl).addCallback(lambda _:True)
+        return DeferredList(dl).addCallback(_defer)
 
     def uninviteUserToShare(self, userid, ace, request):
         """ Send out in uninvite first, and then remove this user from the share list."""
@@ -423,29 +422,41 @@
 
     def inviteUserUpdateToShare(self, userid, cn, aceOLD, aceNEW, summary, request):
 
-        # Check for valid userid first
-        userid, cn = self.validUserIDWithCommonNameForShare(userid, cn)
-        if userid is None:
-            return succeed(False)
-
+        resultIsList = True
         if type(userid) is not list:
             userid = [userid]
+            resultIsList = False
         if type(cn) is not list:
             cn = [cn]
+            
+        def _defer(resultset):
+            results = [result if success else False for success, result in resultset]
+            if resultIsList:
+                return results
+            else:
+                return results[0]
+
         dl = [self.inviteSingleUserUpdateToShare(user, cn, aceOLD, aceNEW, summary, request) for user, cn in zip(userid, cn)]
-        return DeferredList(dl).addCallback(lambda _:True)
+        return DeferredList(dl).addCallback(_defer)
 
     @inlineCallbacks
     def inviteSingleUserToShare(self, userid, cn, ace, summary, request):
         
+        # Validate userid and cn
+        userid, principalURL, cn = self.validUserIDWithCommonNameForShare(userid, cn)
+        
+        # We currently only handle local users
+        if principalURL is None:
+            returnValue(False)
+
         # Look for existing invite and update its fields or create new one
-        record = self.invitesDB().recordForUserID(userid)
+        record = self.invitesDB().recordForPrincipalURL(principalURL)
         if record:
             record.name = cn
             record.access = inviteAccessMapFromXML[type(ace)]
             record.summary = summary
         else:
-            record = Invite(str(uuid4()), userid, cn, inviteAccessMapFromXML[type(ace)], "NEEDS-ACTION", summary)
+            record = Invite(str(uuid4()), userid, principalURL, cn, inviteAccessMapFromXML[type(ace)], "NEEDS-ACTION", summary)
         
         # Send invite
         yield self.sendInvite(record, request)
@@ -455,16 +466,15 @@
         
         returnValue(True)            
 
-    @inlineCallbacks
     def uninviteSingleUserFromShare(self, userid, aces, request):
         
-        newuserid = self.validUserIDForShare(userid)
-        if newuserid:
-            userid = newuserid
-
-        # Cancel invites
+        # Cancel invites - we'll just use whatever userid we are given
         record = self.invitesDB().recordForUserID(userid)
+        return self.uninviteRecordFromShare(record, request) if record else succeed(True)
         
+    @inlineCallbacks
+    def uninviteRecordFromShare(self, record, request):
+        
         # Remove any shared calendar or address book
         sharee = self.principalForCalendarUserAddress(record.userid)
         if sharee:
@@ -483,7 +493,7 @@
                 yield self.sendInvite(record, request)
     
         # Remove from database
-        self.invitesDB().removeRecordForUserID(userid)
+        self.invitesDB().removeRecordForUserID(record.userid)
         
         returnValue(True)            
 
@@ -614,8 +624,7 @@
 
             def _autoShare(isShared, request):
                 if not isShared:
-                    if not self.isCalendarCollection() or config.Sharing.Calendars.AllowScheduling or len(self.listChildren()) == 0:
-                        return self.upgradeToShare(request)
+                    return self.upgradeToShare(request)
                 else:
                     return succeed(True)
                 raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Cannot upgrade to shared calendar"))
@@ -751,9 +760,10 @@
 
 class Invite(object):
     
-    def __init__(self, inviteuid, userid, common_name, access, state, summary):
+    def __init__(self, inviteuid, userid, principalURL, common_name, access, state, summary):
         self.inviteuid = inviteuid
         self.userid = userid
+        self.principalURL = principalURL
         self.name = common_name
         self.access = access
         self.state = state
@@ -800,6 +810,11 @@
         row = self._db_execute("select * from INVITE where USERID = :1", userid)
         return self._makeRecord(row[0]) if row else None
     
+    def recordForPrincipalURL(self, principalURL):
+        
+        row = self._db_execute("select * from INVITE where PRINCIPALURL = :1", principalURL)
+        return self._makeRecord(row[0]) if row else None
+    
     def recordForInviteUID(self, inviteUID):
 
         row = self._db_execute("select * from INVITE where INVITEUID = :1", inviteUID)
@@ -807,15 +822,19 @@
     
     def addOrUpdateRecord(self, record):
 
-        self._db_execute("""insert or replace into INVITE (INVITEUID, USERID, NAME, ACCESS, STATE, SUMMARY)
-            values (:1, :2, :3, :4, :5, :6)
-            """, record.inviteuid, record.userid, record.name, record.access, record.state, record.summary,
+        self._db_execute("""insert or replace into INVITE (INVITEUID, USERID, PRINCIPALURL, NAME, ACCESS, STATE, SUMMARY)
+            values (:1, :2, :3, :4, :5, :6, :7)
+            """, record.inviteuid, record.userid, record.principalURL, record.name, record.access, record.state, record.summary,
         )
     
     def removeRecordForUserID(self, userid):
 
         self._db_execute("delete from INVITE where USERID = :1", userid)
     
+    def removeRecordForPrincipalURL(self, principalURL):
+
+        self._db_execute("delete from INVITE where PRINCIPALURL = :1", principalURL)
+    
     def removeRecordForInviteUID(self, inviteUID):
 
         self._db_execute("delete from INVITE where INVITEUID = :1", inviteUID)
@@ -846,6 +865,7 @@
         # INVITE table is the primary table
         #   INVITEUID: UID for this invite
         #   USERID: identifier of invitee
+        #   PRINCIPALURL: principal-URL of invitee
         #   NAME: common name of invitee
         #   ACCESS: Access mode for share
         #   STATE: Invite response status
@@ -856,6 +876,7 @@
             create table INVITE (
                 INVITEUID      text unique,
                 USERID         text unique,
+                PRINCIPALURL   text unique,
                 NAME           text,
                 ACCESS         text,
                 STATE          text,
@@ -871,6 +892,11 @@
         )
         q.execute(
             """
+            create index PRINCIPALURL on INVITE (PRINCIPALURL)
+            """
+        )
+        q.execute(
+            """
             create index INVITEUID on INVITE (INVITEUID)
             """
         )
@@ -984,7 +1010,7 @@
     @inlineCallbacks
     def declineShare(self, request, hostUrl, inviteUID):
 
-        # Remove it if its in the DB
+        # Remove it if it is in the DB
         yield self.removeShareByUID(request, inviteUID)
 
         yield self._changeShare(request, "DECLINED", hostUrl, inviteUID)

Modified: CalendarServer/branches/new-store/twistedcaldav/static.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/static.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/static.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -57,9 +57,10 @@
 from twext.web2.http import HTTPError, StatusResponse
 from twext.web2.dav import davxml
 from twext.web2.dav.element.base import dav_namespace
-from twext.web2.dav.fileop import mkcollection, rmdir, delete
+from twext.web2.dav.fileop import mkcollection, rmdir
 from twext.web2.dav.http import ErrorResponse
 from twext.web2.dav.idav import IDAVResource
+from twext.web2.dav.method import put_common, delete_common
 from twext.web2.dav.noneprops import NonePropertyStore
 from twext.web2.dav.resource import AccessDeniedError
 from twext.web2.dav.resource import davPrivilegeSet
@@ -1467,15 +1468,25 @@
         
         # TODO: use the generic StoreObject api so that quota, sync-token etc all get changed properly
         child = self.createSimilarFile(self.fp.child(rname).path)
-        child.fp.setContent(xmldata)
-        child.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"}))))
-        child.writeDeadProperty(customxml.NotificationType(xmltype))
-        
-        return succeed(True)
+        def _defer(_):
+            child.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(MimeType("text", "xml", params={"charset":"utf-8"}))))
+            child.writeDeadProperty(customxml.NotificationType(xmltype))
+            return True
 
+        url = request.urlForResource(self)
+        url = joinURL(url, rname)
+        request._rememberResource(child, url)
+        d = put_common.storeResource(request, data=xmldata, destination=child, destination_uri=url)
+        d.addCallback(_defer)
+        return d
+
+
     def _deleteNotification(self, request, rname):
-        childfp = self.fp.child(rname)
-        return delete("", childfp)
+        child = self.createSimilarFile(self.fp.child(rname).path)
+        url = request.urlForResource(self)
+        url = joinURL(url, rname)
+        request._rememberResource(child, url)
+        return delete_common.deleteResource(request, child, url)
 
 class NotificationFile(NotificationResource, CalDAVFile):
 

Modified: CalendarServer/branches/new-store/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/stdconfig.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/stdconfig.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -338,7 +338,6 @@
 
         "Calendars" : {
             "Enabled"         : True,  # Calendar on/off switch
-            "AllowScheduling" : False, # Scheduling in shared calendars
         },
         "AddressBooks" : {
             "Enabled"         : True,  # Address Books on/off switch

Modified: CalendarServer/branches/new-store/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/storebridge.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/storebridge.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -204,8 +204,11 @@
         home = self.parent._newStoreCalendarHome
         storage = home.calendarWithName("inbox")
         if storage is None:
-            # FIXME: spurious error, sanity check, should not be needed
-            raise RuntimeError("backend should be handling this for us")
+            # raise RuntimeError("backend should be handling this for us")
+            # FIXME: spurious error, sanity check, should not be needed;
+            # unfortunately, user09's calendar home does not have an inbox, so
+            # this is a temporary workaround.
+            home.createCalendarWithName("inbox")
             storage = home.calendarWithName("inbox")
         self._initializeWithCalendar(
             storage,

Modified: CalendarServer/branches/new-store/twistedcaldav/test/data/calendar.10.tgz
===================================================================
(Binary files differ)

Modified: CalendarServer/branches/new-store/twistedcaldav/test/data/calendar.100.tgz
===================================================================
(Binary files differ)

Modified: CalendarServer/branches/new-store/twistedcaldav/test/data/calendar.1000.tgz
===================================================================
(Binary files differ)

Modified: CalendarServer/branches/new-store/twistedcaldav/test/data/makelargecalendars.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/data/makelargecalendars.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/test/data/makelargecalendars.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -24,13 +24,16 @@
     wd = os.path.dirname(__file__)
     document_root = "."
     user_max = 99
+    user_one = None
     calendars = ("calendar.10", "calendar.100", "calendar.1000",)
 
-    options, args = getopt.getopt(sys.argv[1:], "n:d:")
+    options, args = getopt.getopt(sys.argv[1:], "n:d:o:")
 
     for option, value in options:
         if option == "-n":
             user_max = int(value)
+        elif option == "-o":
+            user_one = int(value)
         elif option == "-d":
             document_root = os.path.abspath(value)
         else:
@@ -38,7 +41,7 @@
             raise ValueError
 
     
-    for ctr in xrange(1, user_max + 1): 
+    for ctr in (xrange(user_one, user_one + 1) if user_one else xrange(1, user_max + 1)): 
         path = os.path.join(document_root, "calendars/__uids__/us/er/user%02d" % (ctr,))
     
         try: os.makedirs(path)

Modified: CalendarServer/branches/new-store/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/branches/new-store/twistedcaldav/test/test_sharing.py	2010-06-22 23:35:33 UTC (rev 5805)
+++ CalendarServer/branches/new-store/twistedcaldav/test/test_sharing.py	2010-06-22 23:38:46 UTC (rev 5806)
@@ -74,12 +74,12 @@
             return userid
         if userid.endswith("@example.com"):
             principal = SharingTests.FakePrincipal(userid)
-            return principal.path if len(args) == 0 else (principal.path, principal.displayname,)
+            return principal.path if len(args) == 0 else (userid, principal.path, principal.displayname,)
         else:
-            return None if len(args) == 0 else (None, None,)
+            return None if len(args) == 0 else (None, None, None,)
 
     def _fakeInvalidUserID(self, userid, *args):
-        return None if len(args) == 0 else (None, None,)
+        return None if len(args) == 0 else (None, None, None,)
 
     @inlineCallbacks
     def _doPOST(self, body, resultcode = responsecode.OK):
@@ -183,7 +183,7 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user02"),
+                davxml.HRef.fromString("mailto:user02 at example.com"),
                 customxml.CommonName.fromString("USER02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -213,7 +213,7 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user02"),
+                davxml.HRef.fromString("mailto:user02 at example.com"),
                 customxml.CommonName.fromString("USER02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -254,7 +254,7 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user02"),
+                davxml.HRef.fromString("mailto:user02 at example.com"),
                 customxml.CommonName.fromString("USER02"),
                 customxml.InviteAccess(customxml.ReadAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -320,21 +320,21 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user02"),
+                davxml.HRef.fromString("mailto:user02 at example.com"),
                 customxml.CommonName.fromString("USER02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user03"),
+                davxml.HRef.fromString("mailto:user03 at example.com"),
                 customxml.CommonName.fromString("USER03"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user04"),
+                davxml.HRef.fromString("mailto:user04 at example.com"),
                 customxml.CommonName.fromString("USER04"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -377,14 +377,14 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user02"),
+                davxml.HRef.fromString("mailto:user02 at example.com"),
                 customxml.CommonName.fromString("USER02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user04"),
+                davxml.HRef.fromString("mailto:user04 at example.com"),
                 customxml.CommonName.fromString("USER04"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -427,14 +427,14 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user02"),
+                davxml.HRef.fromString("mailto:user02 at example.com"),
                 customxml.CommonName.fromString("USER02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
             ),
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user03"),
+                davxml.HRef.fromString("mailto:user03 at example.com"),
                 customxml.CommonName.fromString("USER03"),
                 customxml.InviteAccess(customxml.ReadAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -491,7 +491,7 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user01"),
+                davxml.HRef.fromString("mailto:user01 at example.com"),
                 customxml.CommonName.fromString("USER01"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusNoResponse(),
@@ -506,7 +506,7 @@
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
-                davxml.HRef.fromString("/principals/__uids__/user01"),
+                davxml.HRef.fromString("mailto:user01 at example.com"),
                 customxml.CommonName.fromString("USER01"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
                 customxml.InviteStatusInvalid(),
@@ -516,7 +516,7 @@
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
 <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
     <CS:remove>
-        <D:href>/principals/__uids__/user01</D:href>
+        <D:href>mailto:user01 at example.com</D:href>
     </CS:remove>
 </CS:share>
 """)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100622/f29dfc49/attachment-0001.html>


More information about the calendarserver-changes mailing list