[CalendarServer-changes] [6737] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri Jan 14 11:23:46 PST 2011


Revision: 6737
          http://trac.macosforge.org/projects/calendarserver/changeset/6737
Author:   cdaboo at apple.com
Date:     2011-01-14 11:23:39 -0800 (Fri, 14 Jan 2011)
Log Message:
-----------
Put the PROPFIND cache back in and use a config EnableXXX option to control it.

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/provision/root.py
    CalendarServer/trunk/calendarserver/provision/test/test_root.py
    CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist
    CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist
    CalendarServer/trunk/calendarserver/tools/util.py
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/conf/resources/caldavd-resources.plist
    CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py
    CalendarServer/trunk/twistedcaldav/extensions.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/stdconfig.py
    CalendarServer/trunk/twistedcaldav/storebridge.py
    CalendarServer/trunk/twistedcaldav/test/test_link.py
    CalendarServer/trunk/twistedcaldav/test/test_resource.py
    CalendarServer/trunk/txdav/caldav/datastore/file.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/carddav/datastore/file.py
    CalendarServer/trunk/txdav/carddav/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/common/datastore/sql.py

Added Paths:
-----------
    CalendarServer/trunk/twistedcaldav/cache.py
    CalendarServer/trunk/twistedcaldav/test/test_cache.py

Property Changed:
----------------
    CalendarServer/trunk/txdav/caldav/datastore/index_file.py
    CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/index_file.py
    CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py

Modified: CalendarServer/trunk/calendarserver/provision/root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/root.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/calendarserver/provision/root.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -28,11 +28,16 @@
 from twisted.internet.defer import inlineCallbacks, returnValue
 from twisted.web.xmlrpc import Proxy
 
+from twistedcaldav.cache import _CachedResponseResource
+from twistedcaldav.cache import MemcacheResponseCache, MemcacheChangeNotifier
+from twistedcaldav.cache import DisabledCache
 from twistedcaldav.config import config
 from twistedcaldav.extensions import DAVFile, CachingPropertyStore
 from twistedcaldav.extensions import DirectoryPrincipalPropertySearchMixIn
 from twistedcaldav.extensions import ReadOnlyResourceMixIn
 from twistedcaldav.resource import CalDAVComplianceMixIn
+from twistedcaldav.resource import CalendarHomeResource, AddressBookHomeResource
+from twistedcaldav.directory.principal import DirectoryPrincipalResource
 
 log = Logger()
 
@@ -67,6 +72,15 @@
 
         self.contentFilters = []
 
+        if config.EnableResponseCache and config.Memcached.Pools.Default.ClientEnabled:
+            self.responseCache = MemcacheResponseCache(self.fp)
+
+            CalendarHomeResource.cacheNotifierFactory = MemcacheChangeNotifier
+            AddressBookHomeResource.cacheNotifierFactory = MemcacheChangeNotifier
+            DirectoryPrincipalResource.cacheNotifierFactory = MemcacheChangeNotifier
+        else:
+            self.responseCache = DisabledCache()
+
         if config.ResponseCompression:
             from twext.web2.filter import gzip
             self.contentFilters.append((gzip.gzipfilter, True))
@@ -297,6 +311,30 @@
                     request.extendedLogItems = {}
                 request.extendedLogItems["xff"] = remote_ip[0]
 
+        if request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1:
+            try:
+                authnUser, authzUser = (yield self.authenticate(request))
+                request.authnUser = authnUser
+                request.authzUser = authzUser
+            except (UnauthorizedLogin, LoginFailed):
+                response = (yield UnauthorizedResponse.makeResponse(
+                    request.credentialFactories,
+                    request.remoteAddr
+                ))
+                raise HTTPError(response)
+
+            try:
+                if not getattr(request, "checkingCache", False):
+                    request.checkingCache = True
+                    response = (yield self.responseCache.getResponseForRequest(request))
+                    if response is None:
+                        request.notInCache = True
+                        raise KeyError("Not found in cache.")
+        
+                    returnValue((_CachedResponseResource(response), []))
+            except KeyError:
+                pass
+
         child = (yield super(RootResource, self).locateChild(request, segments))
         returnValue(child)
 

Modified: CalendarServer/trunk/calendarserver/provision/test/test_root.py
===================================================================
--- CalendarServer/trunk/calendarserver/provision/test/test_root.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/calendarserver/provision/test/test_root.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -366,7 +366,79 @@
         )
         return self.send(request, do_test)
 
+class SACLCacheTests(RootTests):
+    
+    class StubResponseCacheResource(object):
+        def __init__(self):
+            self.cache = {}
+            self.responseCache = self
+            self.cacheHitCount = 0
 
+        def getResponseForRequest(self, request):
+            if str(request) in self.cache:
+                self.cacheHitCount += 1
+                return self.cache[str(request)]
+    
+    
+        def cacheResponseForRequest(self, request, response):
+            self.cache[str(request)] = response
+            return response
+
+    def setUp(self):
+        super(SACLCacheTests, self).setUp()
+        self.root.resource.responseCache = SACLCacheTests.StubResponseCacheResource()
+
+    def test_PROPFIND(self):
+        self.root.resource.useSacls = True
+
+        body = """<?xml version="1.0" encoding="utf-8" ?>
+<D:propfind xmlns:D="DAV:">
+<D:prop>
+<D:getetag/>
+<D:displayname/>
+</D:prop>
+</D:propfind>
+"""
+
+        request = SimpleRequest(
+            self.site,
+            "PROPFIND",
+            "/principals/users/dreid/",
+            headers=http_headers.Headers({
+                    'Authorization': ['basic', '%s' % ('dreid:dierd'.encode('base64'),)],
+                    'Content-Type': 'application/xml; charset="utf-8"',
+                    'Depth':'1',
+            }),
+            content=body
+        )
+
+        def gotResponse1(response):
+            if response.code != responsecode.MULTI_STATUS:
+                self.fail("Incorrect response for PROPFIND /principals/: %s" % (response.code,))
+
+            request = SimpleRequest(
+                self.site,
+                "PROPFIND",
+                "/principals/users/dreid/",
+                headers=http_headers.Headers({
+                        'Authorization': ['basic', '%s' % ('dreid:dierd'.encode('base64'),)],
+                        'Content-Type': 'application/xml; charset="utf-8"',
+                        'Depth':'1',
+                }),
+                content=body
+            )
+
+            d = self.send(request, gotResponse2)
+            return d
+
+        def gotResponse2(response):
+            if response.code != responsecode.MULTI_STATUS:
+                self.fail("Incorrect response for PROPFIND /principals/: %s" % (response.code,))
+            self.assertEqual(self.root.resource.responseCache.cacheHitCount, 1)
+
+        d = self.send(request, gotResponse1)
+        return d
+
 class WikiTests(RootTests):
     
     @inlineCallbacks

Modified: CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/calendarserver/tools/test/gateway/caldavd.plist	2011-01-14 19:23:39 UTC (rev 6737)
@@ -741,7 +741,13 @@
         </dict>
     </dict>
 
+    <!-- Response Caching -->
+    <key>EnableResponseCache</key>
+    <true/>
+    <key>ResponseCacheTimeout</key>
+    <integer>30</integer> <!-- in minutes -->
 
+
     <!--
         Twisted
       -->

Modified: CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/calendarserver/tools/test/principals/caldavd.plist	2011-01-14 19:23:39 UTC (rev 6737)
@@ -741,6 +741,13 @@
         </dict>
     </dict>
 
+    <!-- Response Caching -->
+    <key>EnableResponseCache</key>
+    <true/>
+    <key>ResponseCacheTimeout</key>
+    <integer>30</integer> <!-- in minutes -->
+
+
     <!--
         Twisted
       -->

Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/calendarserver/tools/util.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -157,7 +157,7 @@
     root.putChild("principals", principalCollection)
 
     # Need a data store
-    _newStore = CommonDataStore(FilePath(config.DocumentRoot), True, False)
+    _newStore = CommonDataStore(FilePath(config.DocumentRoot), None, True, False)
 
     from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
     calendarCollection = DirectoryCalendarHomeProvisioningResource(

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2011-01-14 19:23:39 UTC (rev 6737)
@@ -761,6 +761,12 @@
       </array>
     </dict>
 
+    <!-- Response Caching -->
+    <key>EnableResponseCache</key>
+    <true/>
+    <key>ResponseCacheTimeout</key>
+    <integer>30</integer> <!-- in minutes -->
+
     <!-- Support for Postgres -->
     <key>Postgres</key>
     <dict>

Modified: CalendarServer/trunk/conf/resources/caldavd-resources.plist
===================================================================
--- CalendarServer/trunk/conf/resources/caldavd-resources.plist	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/conf/resources/caldavd-resources.plist	2011-01-14 19:23:39 UTC (rev 6737)
@@ -718,7 +718,13 @@
       </array>
     </dict>
 
+    <!-- Response Caching -->
+    <key>EnableResponseCache</key>
+    <true/>
+    <key>ResponseCacheTimeout</key>
+    <integer>30</integer> <!-- in minutes -->
 
+
     <!--
         Twisted
       -->

Copied: CalendarServer/trunk/twistedcaldav/cache.py (from rev 5318, CalendarServer/trunk/twistedcaldav/cache.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/cache.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/cache.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -0,0 +1,366 @@
+##
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import cPickle
+import hashlib
+import uuid
+
+from zope.interface import implements
+
+from twisted.internet.defer import succeed, maybeDeferred
+from twext.web2.dav.util import allDataFromStream
+from twext.web2.http import Response
+from twext.web2.iweb import IResource
+from twext.web2.stream import MemoryStream
+
+from twext.python.log import LoggingMixIn
+
+from twistedcaldav.memcachepool import CachePoolUserMixIn, defaultCachePool
+from twistedcaldav.config import config
+
+
+class DisabledCacheNotifier(object):
+    def __init__(self, *args, **kwargs):
+        pass
+
+    def changed(self):
+        return succeed(None)
+
+
+class DisabledCache(object):
+    def getResponseForRequest(self, request):
+        return succeed(None)
+
+    def cacheResponseForRequest(self, request, response):
+        return succeed(response)
+
+
+class URINotFoundException(Exception):
+    def __init__(self, uri):
+        self.uri = uri
+
+
+    def __repr__(self):
+        return "%s: Could not find URI %r" % (
+            self.__class__.__name__,
+            self.uri)
+
+
+class MemcacheChangeNotifier(LoggingMixIn, CachePoolUserMixIn):
+
+    def __init__(self, resource, cachePool=None, cacheHandle="Default"):
+        self._resource = resource
+        self._cachePool = cachePool
+        self._cachePoolHandle = cacheHandle
+
+    def _newCacheToken(self):
+        return str(uuid.uuid4())
+
+    def changed(self):
+        """
+        Change the cache token for a resource
+
+        return: A L{Deferred} that fires when the token has been changed.
+        """
+        url = self._resource.url()
+
+        self.log_debug("Changing Cache Token for %r" % (url,))
+        return self.getCachePool().set(
+            'cacheToken:%s' % (url,),
+            self._newCacheToken(), expireTime=config.ResponseCacheTimeout*60)
+            
+class BaseResponseCache(LoggingMixIn):
+    """
+    A base class which provides some common operations
+    """
+    def _principalURI(self, principal):
+        return str(principal.children[0])
+
+
+    def _uriNotFound(self, f, uri):
+        f.trap(AttributeError)
+        raise URINotFoundException(uri)
+
+
+    def _getRecordForURI(self, uri, request):
+        def _getRecord(resrc):
+            if hasattr(resrc, 'record'):
+                return resrc.record
+
+        try:
+            return request.locateResource(uri).addCallback(
+                _getRecord).addErrback(self._uriNotFound, uri)
+        except AssertionError:
+            raise URINotFoundException(uri)
+
+
+    def _canonicalizeURIForRequest(self, uri, request):
+        try:
+            return request.locateResource(uri).addCallback(
+                lambda resrc: resrc.url()).addErrback(self._uriNotFound, uri)
+        except AssertionError:
+            raise URINotFoundException(uri)
+
+
+    def _getURIs(self, request):
+        def _getSecondURI(rURI):
+            return self._canonicalizeURIForRequest(
+                self._principalURI(request.authnUser),
+                request).addCallback(lambda pURI: (pURI, rURI))
+
+        d = self._canonicalizeURIForRequest(request.uri, request)
+        d.addCallback(_getSecondURI)
+
+        return d
+
+
+    def _requestKey(self, request):
+        def _getBody(uris):
+            return allDataFromStream(request.stream).addCallback(
+                lambda body: (body, uris))
+
+        def _getKey((requestBody, (pURI, rURI))):
+            if requestBody is not None:
+                request.stream = MemoryStream(requestBody)
+                request.stream.doStartReading = None
+
+            request.cacheKey = (request.method,
+                                pURI,
+                                rURI,
+                                request.headers.getHeader('depth'),
+                                hash(requestBody))
+
+            return request.cacheKey
+
+        d = _getBody((self._principalURI(request.authnUser), request.uri))
+        d.addCallback(_getKey)
+        return d
+
+
+    def _getResponseBody(self, key, response):
+        d1 = allDataFromStream(response.stream)
+        d1.addCallback(lambda responseBody: (key, responseBody))
+        return d1
+
+
+class MemcacheResponseCache(BaseResponseCache, CachePoolUserMixIn):
+    def __init__(self, docroot, cachePool=None):
+        self._docroot = docroot
+        self._cachePool = cachePool
+
+
+    def _tokenForURI(self, uri, cachePoolHandle=None):
+        """
+        Get a property store for the given C{uri}.
+
+        @param uri: The URI we'd like the token for.
+        @return: A C{str} representing the token for the URI.
+        """
+
+        if cachePoolHandle:
+            return defaultCachePool(cachePoolHandle).get('cacheToken:%s' % (uri,))
+        else:
+            return self.getCachePool().get('cacheToken:%s' % (uri,))
+
+
+    def _getTokens(self, request):
+        def _tokensForURIs((pURI, rURI)):
+            tokens = []
+            d1 = self._tokenForURI(pURI, "PrincipalToken")
+            d1.addCallback(tokens.append)
+            d1.addCallback(lambda _ign: self._getRecordForURI(pURI, request))
+            d1.addCallback(lambda dToken: tokens.append(hash(dToken)))
+            d1.addCallback(lambda _ign: self._tokenForURI(rURI))
+            d1.addCallback(tokens.append)
+            d1.addCallback(lambda _ign: tokens)
+            return d1
+
+        d = self._getURIs(request)
+        d.addCallback(_tokensForURIs)
+        return d
+
+
+    def _hashedRequestKey(self, request):
+        def _hashKey(key):
+            oldkey = key
+            request.cacheKey = key = hashlib.md5(
+                ':'.join([str(t) for t in key])).hexdigest()
+            self.log_debug("hashing key for get: %r to %r" % (oldkey, key))
+            return request.cacheKey
+
+        d = self._requestKey(request)
+        d.addCallback(_hashKey)
+        return d
+
+
+    def getResponseForRequest(self, request):
+        def _checkTokens(curTokens, expectedTokens, (code, headers, body)):
+            if curTokens[0] != expectedTokens[0]:
+                self.log_debug(
+                    "Principal token doesn't match for %r: %r != %r" % (
+                        request.cacheKey,
+                        curTokens[0],
+                        expectedTokens[0]))
+                return None
+
+            if curTokens[1] != expectedTokens[1]:
+                self.log_debug(
+                    "Directory Record Token doesn't match for %r: %r != %r" % (
+                        request.cacheKey,
+                        curTokens[1],
+                        expectedTokens[1]))
+                return None
+
+            if curTokens[2] != expectedTokens[2]:
+                self.log_debug(
+                    "URI token doesn't match for %r: %r != %r" % (
+                        request.cacheKey,
+                        curTokens[1],
+                        expectedTokens[1]))
+                return None
+
+            r = Response(code,
+                         stream=MemoryStream(body))
+
+            for key, value in headers.iteritems():
+                r.headers.setRawHeaders(key, value)
+
+            return r
+
+        def _unpickleResponse((flags, value), key):
+            if value is None:
+                self.log_debug("Not in cache: %r" % (key,))
+                return None
+
+            self.log_debug("Found in cache: %r = %r" % (key, value))
+
+            (principalToken, directoryToken, uriToken,
+             resp) = cPickle.loads(value)
+            d2 = self._getTokens(request)
+
+            d2.addCallback(_checkTokens,
+                           (principalToken,
+                            directoryToken,
+                            uriToken),
+                           resp)
+
+            return d2
+
+        def _getCached(key):
+            self.log_debug("Checking cache for: %r" % (key,))
+            d1 = self.getCachePool().get(key)
+            return d1.addCallback(_unpickleResponse, key)
+
+        def _handleExceptions(f):
+            f.trap(URINotFoundException)
+            self.log_debug("Could not locate URI: %r" % (f.value,))
+            return None
+
+        d = self._hashedRequestKey(request)
+        d.addCallback(_getCached)
+        d.addErrback(_handleExceptions)
+        return d
+
+
+    def cacheResponseForRequest(self, request, response):
+        def _makeCacheEntry((pToken, dToken, uToken), (key, responseBody)):
+            cacheEntry = cPickle.dumps(
+                (pToken,
+                 dToken,
+                 uToken,
+                 (response.code,
+                  dict(list(response.headers.getAllRawHeaders())),
+                  responseBody)))
+
+            self.log_debug("Adding to cache: %r = %r" % (key, cacheEntry))
+            return self.getCachePool().set(key, cacheEntry,
+                expireTime=config.ResponseCacheTimeout*60).addCallback(
+                lambda _: response)
+
+        def _cacheResponse((key, responseBody)):
+
+            response.headers.removeHeader('date')
+            response.stream = MemoryStream(responseBody)
+
+            d1 = self._getTokens(request)
+            d1.addCallback(_makeCacheEntry, (key, responseBody))
+            return d1
+
+        def _handleExceptions(f):
+            f.trap(URINotFoundException)
+            self.log_debug("Could not locate URI: %r" % (f.value,))
+            return response
+
+        if hasattr(request, 'cacheKey'):
+            d = succeed(request.cacheKey)
+        else:
+            d = self._hashedRequestKey(request)
+
+        d.addCallback(self._getResponseBody, response)
+        d.addCallback(_cacheResponse)
+        d.addErrback(_handleExceptions)
+        return d
+
+
+class _CachedResponseResource(object):
+    implements(IResource)
+
+    def __init__(self, response):
+        self._response = response
+
+    def renderHTTP(self, request):
+        if not hasattr(request, "extendedLogItems"):
+            request.extendedLogItems = {}
+        request.extendedLogItems["cached"] = "1"
+        return self._response
+
+    def locateChild(self, request, segments):
+        return self, []
+
+
+class PropfindCacheMixin(object):
+    def renderHTTP(self, request):
+        def _cacheResponse(responseCache, response):
+            return responseCache.cacheResponseForRequest(request, response)
+
+        def _getResponseCache(response):
+            d1 = request.locateResource("/")
+            d1.addCallback(lambda resource: resource.responseCache)
+            d1.addCallback(_cacheResponse, response)
+            return d1
+
+        d = maybeDeferred(super(PropfindCacheMixin, self).renderHTTP, request)
+
+        if request.method == 'PROPFIND':
+            d.addCallback(_getResponseCache)
+        return d
+
+    def changeCache(self):
+        if hasattr(self, 'cacheNotifier'):
+            return self.cacheNotifier.changed()
+        else:
+            self.log_debug("%r does not have a cacheNotifier but was changed" % (self,))
+
+class CacheStoreNotifier(object):
+    
+    def __init__(self, resource):
+        self.resource = resource
+    
+    def notify(self, op="update"):
+        self.resource.changeCache()
+
+    def clone(self, label="default", id=None):
+        return self

Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -193,9 +193,22 @@
             principals.append(principal)
             newUIDs.add(principal.principalUID())
 
+        # Get the old set of UIDs
+        oldUIDs = (yield self._index().getMembers(self.uid))
+        
         # Change membership
         yield self.setGroupMemberSetPrincipals(principals)
-
+        
+        # Invalidate the primary principal's cache, and any principal's whose
+        # membership status changed
+        yield self.parent.cacheNotifier.changed()
+        
+        changedUIDs = newUIDs.symmetric_difference(oldUIDs)
+        for uid in changedUIDs:
+            principal = self.pcollection.principalForUID(uid)
+            if principal:
+                yield principal.cacheNotifier.changed()
+            
         returnValue(True)
 
     def setGroupMemberSetPrincipals(self, principals):

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -52,6 +52,7 @@
 
 from twistedcaldav.authkerb import NegotiateCredentials
 from twistedcaldav.config import config
+from twistedcaldav.cache import DisabledCacheNotifier, PropfindCacheMixin
 from twistedcaldav.directory import calendaruserproxy
 from twistedcaldav.directory import augment
 from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
@@ -500,7 +501,7 @@
     def principalCollections(self):
         return self.parent.principalCollections()
 
-class DirectoryPrincipalResource (PermissionsMixIn, DAVPrincipalResource):
+class DirectoryPrincipalResource (PropfindCacheMixin, PermissionsMixIn, DAVPrincipalResource):
     """
     Directory principal resource.
     """
@@ -514,6 +515,8 @@
             davxml.ResourceID.qname(),
         )
 
+    cacheNotifierFactory = DisabledCacheNotifier
+
     def __init__(self, parent, record):
         """
         @param parent: the parent of this resource.
@@ -521,6 +524,8 @@
         """
         super(DirectoryPrincipalResource, self).__init__()
 
+        self.cacheNotifier = self.cacheNotifierFactory(self, cacheHandle="PrincipalToken")
+
         if self.isCollection():
             slash = "/"
         else:

Modified: CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/directory/test/resources/caldavd.plist	2011-01-14 19:23:39 UTC (rev 6737)
@@ -719,7 +719,13 @@
       </array>
     </dict>
 
+    <!-- Response Caching -->
+    <key>EnableResponseCache</key>
+    <true/>
+    <key>ResponseCacheTimeout</key>
+    <integer>30</integer> <!-- in minutes -->
 
+
     <!--
         Twisted
       -->

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -23,6 +23,7 @@
 from twext.web2.dav.resource import AccessDeniedError
 from twext.web2.test.test_server import SimpleRequest
 
+from twistedcaldav.cache import DisabledCacheNotifier
 from twistedcaldav.config import config
 from twistedcaldav.directory import augment, calendaruserproxy
 from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
@@ -298,6 +299,15 @@
     # DirectoryPrincipalResource
     ##
 
+    def test_cacheNotifier(self):
+        """
+        Each DirectoryPrincipalResource should have a cacheNotifier attribute
+        that is an instance of DisabledCacheNotifier
+        """
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            self.failUnless(isinstance(recordResource.cacheNotifier,
+                                       DisabledCacheNotifier))
+
     def test_displayName(self):
         """
         DirectoryPrincipalResource.displayName()
@@ -367,7 +377,7 @@
             os.mkdir(path)
 
             # Need a data store
-            _newStore = CommonDataStore(path, True, False)
+            _newStore = CommonDataStore(path, None, True, False)
 
             provisioningResource = DirectoryCalendarHomeProvisioningResource(
                 directory,

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -20,7 +20,8 @@
 
 from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.test.util import xmlFile, augmentsFile, proxiesFile
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
+from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource,\
+    DirectoryPrincipalResource
 from twistedcaldav.directory.xmlfile import XMLDirectoryService
 
 import twistedcaldav.test.util
@@ -300,7 +301,37 @@
             set(["5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1",
                  "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0"]))
 
+    @inlineCallbacks
+    def test_setGroupMemberSetNotifiesPrincipalCaches(self):
+        class StubCacheNotifier(object):
+            changedCount = 0
+            def changed(self):
+                self.changedCount += 1
+                return succeed(None)
 
+        user = self._getPrincipalByShortName(self.directoryService.recordType_users, "cdaboo")
+
+        proxyGroup = user.getChild("calendar-proxy-write")
+
+        notifier = StubCacheNotifier()
+
+        oldCacheNotifier = DirectoryPrincipalResource.cacheNotifierFactory
+
+        try:
+            DirectoryPrincipalResource.cacheNotifierFactory = (lambda _1, _2, **kwargs: notifier)
+
+            self.assertEquals(notifier.changedCount, 0)
+
+            yield proxyGroup.setGroupMemberSet(
+                davxml.GroupMemberSet(
+                    davxml.HRef.fromString(
+                        "/XMLDirectoryService/__uids__/5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1/")),
+                None)
+
+            self.assertEquals(notifier.changedCount, 1)
+        finally:
+            DirectoryPrincipalResource.cacheNotifierFactory = oldCacheNotifier
+
     def test_proxyFor(self):
 
         return self._proxyForTest(

Modified: CalendarServer/trunk/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/extensions.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/extensions.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -682,7 +682,25 @@
          ))
 
 
+def updateCacheTokenOnCallback(f):
+    def wrapper(self, *args, **kwargs):
+        if hasattr(self, "cacheNotifier"):
+            def updateToken(response):
+                d = self.cacheNotifier.changed()
+                d.addCallback(lambda _: response)
+                return d
 
+            d = maybeDeferred(f, self, *args, **kwargs)
+
+            if hasattr(self, "cacheNotifier"):
+                d.addCallback(updateToken)
+
+            return d
+        else:
+            return f(self, *args, **kwargs)
+
+    return wrapper
+
 class DAVResource (DirectoryPrincipalPropertySearchMixIn,
                    SudoersMixin, SuperDAVResource, LoggingMixIn,
                    DirectoryRenderingMixIn, StaticRenderMixin):
@@ -692,6 +710,22 @@
     Note we add StaticRenderMixin as a base class because we need all the etag etc behavior
     that is currently in static.py but is actually applicable to any type of resource.
     """
+
+    @updateCacheTokenOnCallback
+    def http_PROPPATCH(self, request):
+        return super(DAVResource, self).http_PROPPATCH(request)
+
+
+    @updateCacheTokenOnCallback
+    def http_DELETE(self, request):
+        return super(DAVResource, self).http_DELETE(request)
+
+
+    @updateCacheTokenOnCallback
+    def http_ACL(self, request):
+        return super(DAVResource, self).http_ACL(request)
+
+    
     http_REPORT = http_REPORT
 
     def davComplianceClasses(self):

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -59,6 +59,8 @@
 
 from twistedcaldav import caldavxml, customxml
 from twistedcaldav import carddavxml
+from twistedcaldav.cache import PropfindCacheMixin, DisabledCacheNotifier,\
+    CacheStoreNotifier
 from twistedcaldav.caldavxml import caldav_namespace
 from twistedcaldav.carddavxml import carddav_namespace
 from twistedcaldav.config import config
@@ -176,6 +178,22 @@
 
 calendarPrivilegeSet = _calendarPrivilegeSet()
 
+def updateCacheTokenOnCallback(f):
+    def fun(self, *args, **kwargs):
+        def _updateToken(response):
+            return self.cacheNotifier.changed().addCallback(
+                lambda _: response)
+
+        d = maybeDeferred(f, self, *args, **kwargs)
+
+        if hasattr(self, 'cacheNotifier'):
+            d.addCallback(_updateToken)
+
+        return d
+
+    return fun
+
+
 class CalDAVResource (
         CalDAVComplianceMixIn, SharedCollectionMixin,
         DAVResourceWithChildrenMixin, DAVResource, LoggingMixIn
@@ -343,6 +361,18 @@
 
     # End transitional new-store interface 
 
+    @updateCacheTokenOnCallback
+    def http_PROPPATCH(self, request):
+        return super(CalDAVResource, self).http_PROPPATCH(request)
+
+    @updateCacheTokenOnCallback
+    def http_DELETE(self, request):
+        return super(CalDAVResource, self).http_DELETE(request)
+
+    @updateCacheTokenOnCallback
+    def http_ACL(self, request):
+        return super(CalDAVResource, self).http_ACL(request)
+
     ##
     # WebDAV
     ##
@@ -1935,10 +1965,12 @@
         """
         return None
 
-class CommonHomeResource(SharedHomeMixin, CalDAVResource):
+class CommonHomeResource(PropfindCacheMixin, SharedHomeMixin, CalDAVResource):
     """
     Logic common to Calendar and Addressbook home resources.
     """
+    cacheNotifierFactory = DisabledCacheNotifier
+
     def __init__(self, parent, name, transaction, home):
         self.parent = parent
         self.name = name
@@ -1947,6 +1979,8 @@
         self._provisionedLinks = {}
         self._setupProvisions()
         self._newStoreHome = home
+        self.cacheNotifier = self.cacheNotifierFactory(self)
+        self._newStoreHome.addNotifier(CacheStoreNotifier(self))
         CalDAVResource.__init__(self)
 
         from twistedcaldav.storebridge import _NewStorePropertiesWrapper
@@ -2297,6 +2331,9 @@
     def principalForRecord(self):
         raise NotImplementedError("Subclass must implement principalForRecord()")
 
+    def notifierID(self, label="default"):
+        self._newStoreHome.notifierID(label)
+
     def notifyChanged(self):
         self._newStoreHome.notifyChanged()
 

Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -623,6 +623,9 @@
 
     "EnableKeepAlive": True,
 
+    "EnableResponseCache":  True,
+    "ResponseCacheTimeout": 30, # Minutes
+
     # Specify which opendirectory module to use:
     # "opendirectory" is PyOpenDirectory (the old one which uses
     # DirectoryService.framework)

Modified: CalendarServer/trunk/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/storebridge.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/storebridge.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -465,6 +465,12 @@
         yield self._newStoreObject.rename(basename)
         returnValue(NO_CONTENT)
 
+    def notifierID(self, label="default"):
+        self._newStoreObject.notifierID(label)
+
+    def notifyChanged(self):
+        self._newStoreObject.notifyChanged()
+
 class CalendarCollectionResource(_CommonHomeChildCollectionMixin, CalDAVResource):
     """
     Wrapper around a L{txdav.caldav.icalendar.ICalendar}.

Copied: CalendarServer/trunk/twistedcaldav/test/test_cache.py (from rev 5318, CalendarServer/trunk/twistedcaldav/test/test_cache.py)
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_cache.py	                        (rev 0)
+++ CalendarServer/trunk/twistedcaldav/test/test_cache.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -0,0 +1,479 @@
+##
+# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from new import instancemethod
+import hashlib
+import cPickle
+
+from twisted.internet.defer import succeed, maybeDeferred
+
+from twext.web2.dav import davxml
+from twext.web2.dav.util import allDataFromStream
+from twext.web2.stream import MemoryStream
+from twext.web2.http_headers import Headers
+
+from twistedcaldav.cache import MemcacheResponseCache
+from twistedcaldav.cache import MemcacheChangeNotifier
+from twistedcaldav.cache import PropfindCacheMixin
+
+from twistedcaldav.test.util import InMemoryMemcacheProtocol
+from twistedcaldav.test.util import TestCase
+
+
+def _newCacheToken(self):
+    called = getattr(self, '_called', 0)
+
+    token = 'token%d' % (called,)
+    setattr(self, '_called', called + 1)
+    return token
+
+
+
+class StubRequest(object):
+    resources = {}
+
+    def __init__(self, method, uri, authnUser, depth='1', body=None):
+        self.method = method
+        self.uri = uri
+        self.authnUser = davxml.Principal(davxml.HRef.fromString(authnUser))
+        self.headers = Headers({'depth': depth})
+
+        if body is None:
+            body = "foobar"
+
+        self.body = body
+        self.stream = MemoryStream(body)
+
+
+    def locateResource(self, uri):
+        assert uri[0] == '/', "URI path didn't begin with '/': %s" % (uri,)
+        return succeed(self.resources.get(uri))
+
+
+
+class StubResponse(object):
+    def __init__(self, code, headers, body):
+        self.code = code
+        self.headers = Headers(headers)
+        self.body = body
+        self.stream = MemoryStream(body)
+
+
+
+class StubURLResource(object):
+    def __init__(self, url, record=None):
+        self._url = url
+
+        if record is not None:
+            self.record = record
+
+    def url(self):
+        return self._url
+
+
+
+class MemCacheChangeNotifierTests(TestCase):
+    def setUp(self):
+        TestCase.setUp(self)
+        self.memcache = InMemoryMemcacheProtocol()
+        self.ccn = MemcacheChangeNotifier(
+            StubURLResource(':memory:'),
+            cachePool=self.memcache)
+
+        self.ccn._newCacheToken = instancemethod(_newCacheToken,
+                                                 self.ccn,
+                                                 MemcacheChangeNotifier)
+
+    def assertToken(self, expectedToken):
+        token = self.memcache._cache['cacheToken::memory:'][1]
+        self.assertEquals(token, expectedToken)
+
+
+    def test_cacheTokenPropertyIsProvisioned(self):
+        d = self.ccn.changed()
+        d.addCallback(lambda _: self.assertToken('token0'))
+        return d
+
+
+    def test_changedChangesToken(self):
+        d = self.ccn.changed()
+        d.addCallback(lambda _: self.ccn.changed())
+        d.addCallback(lambda _: self.assertToken('token1'))
+        return d
+
+
+    def tearDown(self):
+        for call in self.memcache._timeouts.itervalues():
+            call.cancel()
+        MemcacheChangeNotifier._memcacheProtocol = None
+
+
+
+class BaseCacheTestMixin(object):
+    def setUp(self):
+        StubRequest.resources = {
+            '/calendars/__uids__/cdaboo/': StubURLResource(
+                '/calendars/__uids__/cdaboo/'),
+            '/calendars/users/cdaboo/': StubURLResource(
+                '/calendars/__uids__/cdaboo/'),
+            '/principals/__uids__/cdaboo/': StubURLResource(
+                '/principals/__uids__/cdaboo/', record='directoryToken0'),
+            '/calendars/__uids__/dreid/': StubURLResource(
+                '/calendars/__uids__/dreid/'),
+            '/principals/__uids__/dreid/': StubURLResource(
+                '/principals/__uids__/dreid/', record='directoryToken0')}
+
+
+    def tearDown(self):
+        StubRequest.resources = {}
+
+
+    def assertResponse(self, response, expected):
+        self.assertNotEquals(response, None, "Got None instead of a response.")
+        self.assertEquals(response.code, expected[0])
+        self.assertEquals(set(response.headers.getAllRawHeaders()),
+                          set(expected[1].getAllRawHeaders()))
+
+        d = allDataFromStream(response.stream)
+        d.addCallback(self.assertEquals, expected[2])
+        return d
+
+
+    def test_getResponseForRequestMultiHomedRequestURI(self):
+        request = StubRequest(
+            'PROPFIND',
+            '/calendars/users/cdaboo/',
+            '/principals/__uids__/cdaboo/')
+
+        d = self.rc.getResponseForRequest(request)
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForRequestURINotFound(self):
+        request = StubRequest(
+            'PROPFIND',
+            '/calendars/__uids__/wsanchez/',
+            '/calendars/__uids__/dreid/')
+
+        d = self.rc.getResponseForRequest(request)
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForRequestMultiHomedPrincipalURI(self):
+        request = StubRequest(
+            'PROPFIND',
+            '/calendars/__uids__/cdaboo/',
+            '/principals/users/cdaboo/')
+
+        d = self.rc.getResponseForRequest(request)
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForRequestNotInCache(self):
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/dreid/',
+                '/principals/__uids__/dreid/'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForRequestInCache(self):
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '/principals/__uids__/cdaboo/'))
+
+        d.addCallback(self.assertResponse, self.expected_response)
+        return d
+
+
+    def test_getResponseForRequestPrincipalTokenChanged(self):
+        self.tokens['/principals/__uids__/cdaboo/'] = 'principalToken1'
+
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '/principals/__uids__/cdaboo/'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForRequestUriTokenChanged(self):
+        self.tokens['/calendars/__uids__/cdaboo/'] = 'uriToken1'
+
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '/principals/__uids__/cdaboo/'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForDepthZero(self):
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '/principals/__uids__/cdaboo/',
+                depth='0'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForBody(self):
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '/principals/__uids__/cdaboo/',
+                body='bazbax'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_getResponseForUnauthenticatedRequest(self):
+        d = self.rc.getResponseForRequest(StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '{DAV:}unauthenticated',
+                body='bazbax'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+    def test_cacheUnauthenticatedResponse(self):
+        expected_response = StubResponse(401, {}, "foobar")
+
+        d = self.rc.cacheResponseForRequest(
+            StubRequest('PROPFIND',
+                        '/calendars/__uids__/cdaboo/',
+                        '{DAV:}unauthenticated'),
+            expected_response)
+
+        d.addCallback(self.assertResponse,
+                      (expected_response.code,
+                       expected_response.headers,
+                       expected_response.body))
+
+        return d
+
+
+    def test_cacheResponseForRequest(self):
+        expected_response = StubResponse(200, {}, "Foobar")
+
+        def _assertResponse(ign):
+            d1 = self.rc.getResponseForRequest(StubRequest(
+                    'PROPFIND',
+                    '/principals/__uids__/dreid/',
+                    '/principals/__uids__/dreid/'))
+
+
+            d1.addCallback(self.assertResponse,
+                           (expected_response.code,
+                            expected_response.headers,
+                            expected_response.body))
+            return d1
+
+
+        d = self.rc.cacheResponseForRequest(
+            StubRequest('PROPFIND',
+                        '/principals/__uids__/dreid/',
+                        '/principals/__uids__/dreid/'),
+            expected_response)
+
+        d.addCallback(_assertResponse)
+        return d
+
+
+    def test_recordHashChangeInvalidatesCache(self):
+        StubRequest.resources[
+            '/principals/__uids__/cdaboo/'].record = 'directoryToken1'
+
+        d = self.rc.getResponseForRequest(
+            StubRequest(
+                'PROPFIND',
+                '/calendars/__uids__/cdaboo/',
+                '/principals/__uids__/cdaboo/'))
+
+        d.addCallback(self.assertEquals, None)
+        return d
+
+
+
+class MemcacheResponseCacheTests(BaseCacheTestMixin, TestCase):
+    def setUp(self):
+        super(MemcacheResponseCacheTests, self).setUp()
+
+        memcacheStub = InMemoryMemcacheProtocol()
+        self.rc = MemcacheResponseCache(None, cachePool=memcacheStub)
+        self.rc.logger.setLevel('debug')
+        self.tokens = {}
+
+        self.tokens['/calendars/__uids__/cdaboo/'] = 'uriToken0'
+        self.tokens['/principals/__uids__/cdaboo/'] = 'principalToken0'
+        self.tokens['/principals/__uids__/dreid/'] = 'principalTokenX'
+
+        def _getToken(uri, cachePoolHandle=None):
+            return succeed(self.tokens.get(uri))
+
+        self.rc._tokenForURI = _getToken
+
+        self.expected_response = (200, Headers({}), "Foo")
+
+        expected_key = hashlib.md5(':'.join([str(t) for t in (
+                'PROPFIND',
+                '/principals/__uids__/cdaboo/',
+                '/calendars/__uids__/cdaboo/',
+                '1',
+                hash('foobar'),
+                )])).hexdigest()
+
+        memcacheStub._cache[expected_key] = (
+            0, #flags
+            cPickle.dumps((
+            'principalToken0',
+            hash('directoryToken0'),
+            'uriToken0',
+            (self.expected_response[0],
+             dict(list(self.expected_response[1].getAllRawHeaders())),
+             self.expected_response[2]))))
+
+        self.memcacheStub = memcacheStub
+
+    def tearDown(self):
+        for call in self.memcacheStub._timeouts.itervalues():
+            call.cancel()
+
+    def test_givenURIsForKeys(self):
+        expected_response = (200, Headers({}), "Foobarbaz")
+
+        _key = (
+                'PROPFIND',
+                '/principals/__uids__/cdaboo/',
+                '/calendars/users/cdaboo/',
+                '1',
+                hash('foobar'),
+                )
+
+        expected_key = hashlib.md5(':'.join([str(t) for t in _key])).hexdigest()
+
+        self.memcacheStub._cache[expected_key] = (
+            0, #flags
+            cPickle.dumps((
+                    'principalToken0',
+                    hash('directoryToken0'),
+                    'uriToken0',
+                    (expected_response[0],
+                     dict(list(expected_response[1].getAllRawHeaders())),
+                     expected_response[2]))))
+
+        d = self.rc.getResponseForRequest(
+            StubRequest('PROPFIND',
+                        '/calendars/users/cdaboo/',
+                        '/principals/__uids__/cdaboo/'))
+
+        d.addCallback(self.assertResponse, expected_response)
+        return d
+
+
+
+class StubResponseCacheResource(object):
+    def __init__(self):
+        self.cache = {}
+        self.responseCache = self
+
+
+    def getResponseForRequest(self, request):
+        if request in self.cache:
+            return self.cache[request]
+
+
+    def cacheResponseForRequest(self, request, response):
+        self.cache[request] = response
+        return response
+
+
+
+class TestRenderMixin(object):
+    davHeaders = ('foo',)
+
+    def renderHTTP(self, request):
+        self.response.headers.setHeader('dav', self.davHeaders)
+
+        return self.response
+
+
+
+class TestCachingResource(PropfindCacheMixin, TestRenderMixin):
+    def __init__(self, response):
+        self.response = response
+
+
+
+class PropfindCacheMixinTests(TestCase):
+    """
+    Test the PropfindCacheMixin
+    """
+    def setUp(self):
+        TestCase.setUp(self)
+        self.resource = TestCachingResource(StubResponse(200, {}, "foobar"))
+        self.responseCache = StubResponseCacheResource()
+
+    def test_DAVHeaderCached(self):
+        """
+        Test that the DAV header set in renderHTTP is cached.
+        """
+        def _checkCache(response):
+            self.assertEquals(response.headers.getHeader('dav'),
+                              ('foo',))
+            self.assertEquals(
+                self.responseCache.cache[request].headers.getHeader('dav'),
+                ('foo',))
+
+        request = StubRequest('PROPFIND', '/', '/')
+        request.resources['/'] = self.responseCache
+
+        d = maybeDeferred(self.resource.renderHTTP, request)
+        d.addCallback(_checkCache)
+
+        return d
+
+
+    def test_onlyCachePropfind(self):
+        """
+        Test that we only cache the result of a propfind request.
+        """
+        def _checkCache(response):
+            self.assertEquals(self.responseCache.getResponseForRequest(request),
+                              None)
+
+        request = StubRequest('GET', '/', '/')
+        request.resources['/'] = self.responseCache
+
+        d = maybeDeferred(self.resource.renderHTTP, request)
+        d.addCallback(_checkCache)
+
+        return d

Modified: CalendarServer/trunk/twistedcaldav/test/test_link.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_link.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/test/test_link.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -36,6 +36,9 @@
 
     def calendarWithName(self, name):
         return succeed(None)
+    
+    def addNotifier(self, notifier):
+        pass
 
 class StubCalendarHomeResource(CalendarHomeResource):
     def principalForRecord(self):

Modified: CalendarServer/trunk/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_resource.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/twistedcaldav/test/test_resource.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -27,6 +27,9 @@
 class StubHome(object):
     def properties(self):
         return []
+    
+    def addNotifier(self, notifier):
+        pass
 
 
 class CalDAVResourceTests(TestCase):

Modified: CalendarServer/trunk/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/file.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/txdav/caldav/datastore/file.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -85,8 +85,8 @@
     _topPath = "calendars"
     _notifierPrefix = "CalDAV"
 
-    def __init__(self, uid, path, calendarStore, transaction, notifier):
-        super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
+    def __init__(self, uid, path, calendarStore, transaction, notifiers):
+        super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifiers)
 
         self._childClass = Calendar
 


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -74,10 +74,10 @@
     _notifierPrefix = "CalDAV"
     _revisionsTable = CALENDAR_OBJECT_REVISIONS_TABLE
 
-    def __init__(self, transaction, ownerUID, notifier):
+    def __init__(self, transaction, ownerUID, notifiers):
 
         self._childClass = Calendar
-        super(CalendarHome, self).__init__(transaction, ownerUID, notifier)
+        super(CalendarHome, self).__init__(transaction, ownerUID, notifiers)
         self._shares = SQLLegacyCalendarShares(self)
 
     createCalendarWithName = CommonHome.createChildWithName


Property changes on: CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394

Modified: CalendarServer/trunk/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/file.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/txdav/carddav/datastore/file.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -67,8 +67,8 @@
     _topPath = "addressbooks"
     _notifierPrefix = "CardDAV"
 
-    def __init__(self, uid, path, addressbookStore, transaction, notifier):
-        super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
+    def __init__(self, uid, path, addressbookStore, transaction, notifiers):
+        super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifiers)
 
         self._childClass = AddressBook
 


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/sql.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/txdav/carddav/datastore/sql.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -66,10 +66,10 @@
     _notifierPrefix = "CardDAV"
     _revisionsTable = ADDRESSBOOK_OBJECT_REVISIONS_TABLE
 
-    def __init__(self, transaction, ownerUID, notifier):
+    def __init__(self, transaction, ownerUID, notifiers):
 
         self._childClass = AddressBook
-        super(AddressBookHome, self).__init__(transaction, ownerUID, notifier)
+        super(AddressBookHome, self).__init__(transaction, ownerUID, notifiers)
         self._shares = SQLLegacyAddressBookShares(self)
 
 


Property changes on: CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
   + /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369-6445
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -238,12 +238,12 @@
     _topPath = None
     _notifierPrefix = None
 
-    def __init__(self, uid, path, dataStore, transaction, notifier):
+    def __init__(self, uid, path, dataStore, transaction, notifiers):
         self._dataStore = dataStore
         self._uid = uid
         self._path = path
         self._transaction = transaction
-        self._notifier = notifier
+        self._notifiers = notifiers
         self._shares = SharedCollectionsDatabase(StubResource(self))
         self._newChildren = {}
         self._removedChildren = set()
@@ -297,12 +297,12 @@
             homePath = childPath
 
         if txn._notifierFactory:
-            notifier = txn._notifierFactory.newNotifier(id=uid,
-                prefix=cls._notifierPrefix)
+            notifiers = (txn._notifierFactory.newNotifier(id=uid,
+                prefix=cls._notifierPrefix),)
         else:
-            notifier = None
+            notifiers = None
 
-        home = cls(uid, homePath, txn._dataStore, txn, notifier)
+        home = cls(uid, homePath, txn._dataStore, txn, notifiers)
         if creating:
             home.createdHome()
             if withNotifications:
@@ -502,9 +502,14 @@
         self.properties()[PropertyName.fromElement(TwistedQuotaUsedProperty)] = TwistedQuotaUsedProperty(str(new_used))
             
 
+    def addNotifier(self, notifier):
+        if self._notifiers is None:
+            self._notifiers = ()
+        self._notifiers += (notifier,)
+ 
     def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
+        if self._notifiers:
+            return self._notifiers[0].getID(label)
         else:
             return None
 
@@ -512,8 +517,9 @@
         """
         Trigger a notification of a change
         """
-        if self._notifier:
-            self._transaction.postCommit(self._notifier.notify)
+        if self._notifiers:
+            for notifier in self._notifiers:
+                self._transaction.postCommit(notifier.notify)
 
 
 class CommonHomeChild(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):
@@ -550,12 +556,12 @@
         self._invites = None # Derived classes need to set this
         self._renamedName = realName
 
-        if home._notifier:
+        if home._notifiers:
             childID = "%s/%s" % (home.uid(), name)
-            notifier = home._notifier.clone(label="collection", id=childID)
+            notifiers = [notifier.clone(label="collection", id=childID) for notifier in home._notifiers]
         else:
-            notifier = None
-        self._notifier = notifier
+            notifiers = None
+        self._notifiers = notifiers
 
 
     @classmethod
@@ -819,9 +825,14 @@
     def _doValidate(self, component):
         raise NotImplementedError
 
+    def addNotifier(self, notifier):
+        if self._notifiers is None:
+            self._notifiers = ()
+        self._notifiers += (notifier,)
+ 
     def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
+        if self._notifiers:
+            return self._notifiers[0].getID(label)
         else:
             return None
 
@@ -829,8 +840,9 @@
         """
         Trigger a notification of a change
         """
-        if self._notifier:
-            self._transaction.postCommit(self._notifier.notify)
+        if self._notifiers:
+            for notifier in self._notifiers:
+                self._transaction.postCommit(notifier.notify)
 
 
 class CommonObjectResource(FileMetaDataMixin, LoggingMixIn, FancyEqMixin):

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2011-01-14 02:19:06 UTC (rev 6736)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2011-01-14 19:23:39 UTC (rev 6737)
@@ -243,7 +243,7 @@
     _revisionsTable = None
     _notificationRevisionsTable = NOTIFICATION_OBJECT_REVISIONS_TABLE
 
-    def __init__(self, transaction, ownerUID, notifier):
+    def __init__(self, transaction, ownerUID, notifiers):
         self._txn = transaction
         self._ownerUID = ownerUID
         self._resourceID = None
@@ -251,7 +251,7 @@
         self._childrenLoaded = False
         self._children = {}
         self._sharedChildren = {}
-        self._notifier = notifier
+        self._notifiers = notifiers
         self._quotaUsedBytes = None
 
         # Needed for REVISION/BIND table join
@@ -285,12 +285,12 @@
     def homeWithUID(cls, txn, uid, create=False):
 
         if txn._notifierFactory:
-            notifier = txn._notifierFactory.newNotifier(
+            notifiers = (txn._notifierFactory.newNotifier(
                 id=uid, prefix=cls._notifierPrefix
-            )
+            ),)
         else:
-            notifier = None
-        homeObject = cls(txn, uid, notifier)
+            notifiers = None
+        homeObject = cls(txn, uid, notifiers)
         homeObject = (yield homeObject.initFromStore())
         if homeObject is not None:
             returnValue(homeObject)
@@ -657,9 +657,14 @@
             self._quotaUsedBytes = 0
 
 
+    def addNotifier(self, notifier):
+        if self._notifiers is None:
+            self._notifiers = ()
+        self._notifiers += (notifier,)
+ 
     def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
+        if self._notifiers:
+            return self._notifiers[0].getID(label)
         else:
             return None
 
@@ -667,8 +672,9 @@
         """
         Trigger a notification of a change
         """
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
+        if self._notifiers:
+            for notifier in self._notifiers:
+                self._txn.postCommit(notifier.notify)
 
 
 class CommonHomeChild(LoggingMixIn, FancyEqMixin):
@@ -696,12 +702,12 @@
         self._objectNames = None
         self._syncTokenRevision = None
 
-        if home._notifier:
+        if home._notifiers:
             childID = "%s/%s" % (home.uid(), name)
-            notifier = home._notifier.clone(label="collection", id=childID)
+            notifiers = [notifier.clone(label="collection", id=childID) for notifier in home._notifiers]
         else:
-            notifier = None
-        self._notifier = notifier
+            notifiers = None
+        self._notifiers = notifiers
 
         self._index = None  # Derived classes need to set this
         self._invites = None # Derived classes need to set this
@@ -1392,19 +1398,24 @@
         return datetimeMktime(datetime.datetime.strptime(self._modified, "%Y-%m-%d %H:%M:%S.%f")) if self._modified else None
 
 
+    def addNotifier(self, notifier):
+        if self._notifiers is None:
+            self._notifiers = ()
+        self._notifiers += (notifier,)
+ 
     def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
+        if self._notifiers:
+            return self._notifiers[0].getID(label)
         else:
             return None
 
-
     def notifyChanged(self):
         """
         Trigger a notification of a change
         """
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
+        if self._notifiers:
+            for notifier in self._notifiers:
+                self._txn.postCommit(notifier.notify)
 
 
 
@@ -1677,9 +1688,10 @@
                 prefix=txn._homeClass[txn._primaryHomeType]._notifierPrefix
             )
             notifier.addID(id=uid)
+            notifiers = (notifier,)
         else:
-            notifier = None
-        self._notifier = notifier
+            notifiers = None
+        self._notifiers = notifiers
 
     @classmethod
     @inlineCallbacks
@@ -1945,8 +1957,8 @@
 
 
     def notifierID(self, label="default"):
-        if self._notifier:
-            return self._notifier.getID(label)
+        if self._notifiers:
+            return self._notifiers[0].getID(label)
         else:
             return None
 
@@ -1955,8 +1967,9 @@
         """
         Trigger a notification of a change
         """
-        if self._notifier:
-            self._txn.postCommit(self._notifier.notify)
+        if self._notifiers:
+            for notifier in self._notifiers:
+                self._txn.postCommit(notifier.notify)
 
 
 class NotificationObject(LoggingMixIn, FancyEqMixin):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110114/72a8a47e/attachment-0001.html>


More information about the calendarserver-changes mailing list