[CalendarServer-changes] [5964] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Aug 2 10:53:35 PDT 2010


Revision: 5964
          http://trac.macosforge.org/projects/calendarserver/changeset/5964
Author:   sagen at apple.com
Date:     2010-08-02 10:53:35 -0700 (Mon, 02 Aug 2010)
Log Message:
-----------
Hook push notification into new datastore

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tap/util.py
    CalendarServer/trunk/calendarserver/tools/gateway.py
    CalendarServer/trunk/calendarserver/tools/principals.py
    CalendarServer/trunk/calendarserver/tools/purge.py
    CalendarServer/trunk/calendarserver/tools/resources.py
    CalendarServer/trunk/calendarserver/tools/util.py
    CalendarServer/trunk/twistedcaldav/notify.py
    CalendarServer/trunk/twistedcaldav/static.py
    CalendarServer/trunk/twistedcaldav/test/test_notify.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/txcaldav/calendarstore/file.py
    CalendarServer/trunk/txcaldav/calendarstore/test/common.py
    CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py
    CalendarServer/trunk/txcarddav/addressbookstore/file.py
    CalendarServer/trunk/txcarddav/addressbookstore/test/common.py
    CalendarServer/trunk/txcarddav/addressbookstore/test/test_file.py
    CalendarServer/trunk/txdav/common/datastore/file.py
    CalendarServer/trunk/txdav/datastore/file.py
    CalendarServer/trunk/txdav/idav.py

Modified: CalendarServer/trunk/calendarserver/tap/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tap/util.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/calendarserver/tap/util.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -45,7 +45,7 @@
 from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.util import NotFilePath
 from twistedcaldav.directory.wiki import WikiDirectoryService
-from twistedcaldav.notify import installNotificationClient
+from twistedcaldav.notify import NotifierFactory
 from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
 from twistedcaldav.simpleresource import SimpleResource
 from twistedcaldav.static import CalendarHomeProvisioningFile
@@ -202,15 +202,6 @@
     )
 
     #
-    # Configure NotificationClient
-    #
-    if config.Notifications.Enabled:
-        installNotificationClient(
-            config.Notifications.InternalNotificationHost,
-            config.Notifications.InternalNotificationPort,
-        )
-
-    #
     # Configure the Site and Wrappers
     #
     credentialFactories = []
@@ -281,8 +272,21 @@
 
     principalCollection = principalResourceClass("/principals/", directory)
 
+    #
+    # Configure NotifierFactory
+    #
+    if config.Notifications.Enabled:
+        notifierFactory = NotifierFactory(
+            config.Notifications.InternalNotificationHost,
+            config.Notifications.InternalNotificationPort,
+        )
+    else:
+        notifierFactory = None
+
+
     # Need a data store
-    _newStore = CommonDataStore(FilePath(config.DocumentRoot), config.EnableCalDAV, config.EnableCardDAV)
+    _newStore = CommonDataStore(FilePath(config.DocumentRoot),
+        notifierFactory, config.EnableCalDAV, config.EnableCardDAV)
 
     if config.EnableCalDAV:
         log.info("Setting up calendar collection: %r" % (calendarResourceClass,))

Modified: CalendarServer/trunk/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/gateway.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/calendarserver/tools/gateway.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -32,7 +32,7 @@
 from twistedcaldav.directory.directory import DirectoryError
 from twext.web2.dav import davxml
 
-from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, setupNotifications, checkDirectory
+from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, checkDirectory
 from calendarserver.tools.principals import (
     principalForPrincipalID, proxySubprincipal, addProxy, removeProxy,
     ProxyError, ProxyWarning, updateRecord
@@ -118,7 +118,6 @@
             respondWithError(str(e))
             return
         setupMemcached(config)
-        setupNotifications(config)
     except ConfigurationError, e:
         respondWithError(e)
         return

Modified: CalendarServer/trunk/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/principals.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/calendarserver/tools/principals.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -37,7 +37,7 @@
 from twistedcaldav.directory.directory import UnknownRecordTypeError, DirectoryError
 from twistedcaldav.directory import augment
 
-from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, setupNotifications, booleanArgument, checkDirectory
+from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached,  booleanArgument, checkDirectory
 
 __all__ = [
     "principalForPrincipalID", "proxySubprincipal", "addProxy", "removeProxy",
@@ -248,7 +248,6 @@
         except DirectoryError, e:
             abort(e)
         setupMemcached(config)
-        setupNotifications(config)
     except ConfigurationError, e:
         abort(e)
 

Modified: CalendarServer/trunk/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/purge.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/calendarserver/tools/purge.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -19,7 +19,7 @@
 from calendarserver.tap.util import FakeRequest
 from calendarserver.tap.util import getRootResource
 from calendarserver.tools.principals import removeProxy
-from calendarserver.tools.util import loadConfig, setupMemcached, setupNotifications
+from calendarserver.tools.util import loadConfig, setupMemcached
 from datetime import date, timedelta, datetime
 from getopt import getopt, GetoptError
 from grp import getgrnam
@@ -101,7 +101,6 @@
             print "Error: %s" % (e,)
             return
         setupMemcached(config)
-        setupNotifications(config)
     except ConfigurationError, e:
         print "Error: %s" % (e,)
         return

Modified: CalendarServer/trunk/calendarserver/tools/resources.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/resources.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/calendarserver/tools/resources.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -17,7 +17,7 @@
 ##
 
 from calendarserver.tools.principals import updateRecord
-from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, setupNotifications, checkDirectory
+from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, checkDirectory
 from getopt import getopt, GetoptError
 from grp import getgrnam
 from pwd import getpwnam
@@ -130,7 +130,6 @@
         except DirectoryError, e:
             abort(e)
         setupMemcached(config)
-        setupNotifications(config)
     except ConfigurationError, e:
         abort(e)
 

Modified: CalendarServer/trunk/calendarserver/tools/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/util.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/calendarserver/tools/util.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -39,7 +39,7 @@
 from twistedcaldav.directory import augment, calendaruserproxy
 from twistedcaldav.directory.aggregate import AggregateDirectoryService
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, DirectoryError
-from twistedcaldav.notify import installNotificationClient
+from twistedcaldav.notify import NotifierFactory
 from twistedcaldav.static import CalendarHomeProvisioningFile
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
 
@@ -64,8 +64,17 @@
         def getPrincipalCollection(self):
             if not hasattr(self, "_principalCollection"):
 
+                if config.Notifications.Enabled:
+                    notifierFactory = NotifierFactory(
+                        config.Notifications.InternalNotificationHost,
+                        config.Notifications.InternalNotificationPort,
+                    )
+                else:
+                    notifierFactory = None
+
                 # Need a data store
-                _newStore = CommonDataStore(FilePath(config.DocumentRoot), True, False)
+                _newStore = CommonDataStore(FilePath(config.DocumentRoot), 
+                    notifierFactory, True, False)
 
                 #
                 # Instantiating a CalendarHomeProvisioningResource with a directory
@@ -208,15 +217,6 @@
     )
     autoDisableMemcached(config)
 
-def setupNotifications(config):
-    #
-    # Connect to notifications
-    #
-    if config.Notifications.Enabled:
-        installNotificationClient(
-            config.Notifications.InternalNotificationHost,
-            config.Notifications.InternalNotificationPort,
-        )
 
 def checkDirectory(dirpath, description, access=None, create=None):
     if not os.path.exists(dirpath):

Modified: CalendarServer/trunk/twistedcaldav/notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/notify.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/twistedcaldav/notify.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -26,9 +26,6 @@
 services -- one handling the internal channel between icalserver
 and notification server, the other handling the external channel
 between notification server and a remote consumer.
-
-The icalserver tap creates a NotificationClient object at startup;
-it deals with passing along notifications to the notification server.
 """
 
 # TODO: add CalDAVTester test for examining new xmpp-uri property
@@ -59,28 +56,26 @@
 from twistedcaldav import memcachepool
 
 __all__ = [
-    "ClientNotifier",
     "Coalescer",
-    "getNodeCacher",
-    "getNotificationClient",
-    "getPubSubConfiguration",
-    "getPubSubHeartbeatURI",
-    "getPubSubPath",
-    "getPubSubXMPPURI",
     "INotifier",
-    "installNotificationClient",
     "InternalNotificationFactory",
     "InternalNotificationProtocol",
-    "NotificationClient",
     "NotificationClientFactory",
     "NotificationClientLineProtocol",
     "NotificationServiceMaker",
+    "Notifier",
+    "NotifierFactory",
     "SimpleLineNotificationFactory",
     "SimpleLineNotificationProtocol",
     "SimpleLineNotifier",
     "SimpleLineNotifierService",
     "XMPPNotificationFactory",
     "XMPPNotifier",
+    "getNodeCacher",
+    "getPubSubConfiguration",
+    "getPubSubHeartbeatURI",
+    "getPubSubPath",
+    "getPubSubXMPPURI",
 ]
 
 # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -111,16 +106,16 @@
 # Classes used within calendarserver itself
 #
 
-class ClientNotifier(LoggingMixIn):
+class Notifier(LoggingMixIn):
     """
     Provides a hook for sending changs notifications to the
-    L{NotificationClient}.
+    L{NotifierFactory}.
     """
 
-    def __init__(self, resource, label="default", id=None, configOverride=None):
-        self.ids = { label : (resource, self.normalizeID(id)) }
+    def __init__(self, notifierFactory, label="default", id=None):
+        self._notifierFactory = notifierFactory
+        self._ids = { label : self.normalizeID(id) }
         self._notify = True
-        self.config = configOverride or config
 
     def normalizeID(self, id):
         urn = "urn:uuid:"
@@ -131,47 +126,36 @@
             pass
         return id
 
-    def addResource(self, resource, label="default", id=None):
-        self.ids[label] = (resource, self.normalizeID(id))
-
     def enableNotify(self, arg):
-        self.log_debug("enableNotify: %s" % (self.ids['default'][1],))
+        self.log_debug("enableNotify: %s" % (self._ids['default'][1],))
         self._notify = True
 
     def disableNotify(self):
-        self.log_debug("disableNotify: %s" % (self.ids['default'][1],))
+        self.log_debug("disableNotify: %s" % (self._ids['default'][1],))
         self._notify = False
 
     def notify(self, op="update"):
-        if self.config.Notifications.Enabled:
-            notificationClient = getNotificationClient()
-            for label, (resource, id) in self.ids.iteritems():
-                if id is None:
-                    id = self.getID(label=label)
-                if id is not None:
-                    if self._notify:
-                        self.log_debug("Notifications are enabled: %s %s %s" %
-                            (op, label, id))
-                        notificationClient.send(op, id)
-                    else:
-                        self.log_debug("Skipping notification for: %s" % (id,))
+        for label, id in self._ids.iteritems():
+            if id is None:
+                id = self.getID(label=label)
+            if id is not None:
+                if self._notify:
+                    self.log_debug("Notifications are enabled: %s %s %s" %
+                        (op, label, id))
+                    self._notifierFactory.send(op, id)
+                else:
+                    self.log_debug("Skipping notification for: %s" % (id,))
 
-    def clone(self, resource, label="default", id=None):
-        newNotifier = self.__class__(None, configOverride=self.config)
-        newNotifier.ids = self.ids.copy()
-        newNotifier.ids[label] = (resource, id)
+    def clone(self, label="default", id=None):
+        newNotifier = self.__class__(self._notifierFactory)
+        newNotifier._ids = self._ids.copy()
+        newNotifier._ids[label] = id
         return newNotifier
 
     def getID(self, label="default"):
-        resource, id = self.ids.get(label, (None, None))
-        if id is not None:
-            return id
-        if resource is not None:
-            id = self.normalizeID(resource.resourceID())
-            self.ids[label] = (resource, id)
-            return id
-        return None
+        return self._ids.get(label, None)
 
+
 class NotificationClientLineProtocol(LineReceiver, LoggingMixIn):
     """
     Notification Client Line Protocol
@@ -226,13 +210,12 @@
         return p
 
 
-class NotificationClient(LoggingMixIn):
+class NotifierFactory(LoggingMixIn):
     """
-    Notification Client
+    Notifier Factory
 
-    Forwards on notifications from ClientNotifiers to the
-    notification server.  A NotificationClient is installed by the tap at
-    startup.
+    Creates Notifier instances and forwards notifications from them to the
+    gateway.
     """
 
     def __init__(self, host, port, reactor=None):
@@ -275,18 +258,12 @@
     def removeObserver(self, observer):
         self.observers.remove(observer)
 
+    def newNotifier(self, label="default", id=None):
+        return Notifier(self, label=label, id=id)
 
-_notificationClient = None
 
-def installNotificationClient(host, port, klass=NotificationClient, reactor=None):
-    global _notificationClient
-    _notificationClient = klass(host, port, reactor=reactor)
 
-def getNotificationClient():
-    return _notificationClient
 
-
-
 class NodeCreationException(Exception):
     pass
 

Modified: CalendarServer/trunk/twistedcaldav/static.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/static.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/twistedcaldav/static.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -109,7 +109,7 @@
 from twistedcaldav.vcardindex import AddressBookIndex
 from twistedcaldav.notify import getPubSubConfiguration, getPubSubXMPPURI
 from twistedcaldav.notify import getPubSubHeartbeatURI, getPubSubPath
-from twistedcaldav.notify import ClientNotifier, getNodeCacher
+from twistedcaldav.notify import Notifier, getNodeCacher
 from twistedcaldav.notifications import NotificationCollectionResource,\
     NotificationResource
 
@@ -1045,9 +1045,6 @@
 
         self.associateWithTransaction(transaction)
 
-        # TODO: when calendar home gets a resourceID( ) method, remove
-        # the "id=record.uid" keyword from this call:
-        self.clientNotifier = ClientNotifier(self, id=record.uid)
         storeHome = transaction.calendarHomeWithUID(record.uid)
         if storeHome is not None:
             created = False
@@ -1129,8 +1126,6 @@
 
         if cls is not None:
             child = cls(self.fp.child(name).path, self)
-            child.clientNotifier = self.clientNotifier.clone(child,
-                label="collection")
             return child
         return self.createSimilarFile(self.fp.child(name).path)
 
@@ -1145,8 +1140,6 @@
             path, self,
         )
         self.propagateTransaction(similar)
-        similar.clientNotifier = self.clientNotifier.clone(similar,
-            label="collection")
         return similar
 
     def createSimilarFile(self, path):
@@ -1174,8 +1167,6 @@
                     path, principalCollections=self.principalCollections()
                 )
             self.propagateTransaction(similar)
-            similar.clientNotifier = self.clientNotifier.clone(similar,
-                label="collection")
             return similar
 
     def getChild(self, name):
@@ -1725,7 +1716,7 @@
 
         # TODO: when addressbook home gets a resourceID( ) method, remove
         # the "id=record.uid" keyword from this call:
-        self.clientNotifier = ClientNotifier(self, id=record.uid)
+        # self.clientNotifier = ClientNotifier(self, id=record.uid)
         self._newStoreAddressBookHome = (
             transaction.addressbookHomeWithUID(record.uid, create=True)
         )
@@ -1785,8 +1776,6 @@
 
         if cls is not None:
             child = cls(self.fp.child(name).path, self)
-            child.clientNotifier = self.clientNotifier.clone(child,
-                label="collection")
             return child
         return self.createSimilarFile(self.fp.child(name).path)
 
@@ -1826,8 +1815,6 @@
                     path, principalCollections=self.principalCollections()
                 )
             self.propagateTransaction(similar)
-            similar.clientNotifier = self.clientNotifier.clone(similar,
-                label="collection")
             return similar
 
     def getChild(self, name):
@@ -1997,15 +1984,13 @@
     """
     def __init__(self, path, principalCollections):
         CalDAVFile.__init__(self, path, principalCollections=principalCollections)
-        self.clientNotifier = ClientNotifier(self)
+        # self.clientNotifier = ClientNotifier(self)
 
     def createSimilarFile(self, path):
         if self.comparePath(path):
             return self
         else:
             similar = CalDAVFile(path, principalCollections=self.principalCollections())
-            similar.clientNotifier = self.clientNotifier.clone(similar,
-                label="collection")
             return similar
 
 ##

Modified: CalendarServer/trunk/twistedcaldav/test/test_notify.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_notify.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/twistedcaldav/test/test_notify.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -32,59 +32,34 @@
         return self._id
 
 
-class NotificationClientUserTests(TestCase):
 
-    def test_installNotificationClient(self):
-        self.assertEquals(getNotificationClient(), None)
-        self.clock = Clock()
-        installNotificationClient(None, None,
-            klass=StubNotificationClient, reactor=self.clock)
-        notificationClient = getNotificationClient()
-        self.assertNotEquals(notificationClient, None)
+class NotifierTests(TestCase):
 
+    def test_notifier(self):
         enabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
         enabledConfig.Notifications["Enabled"] = True
-        resource = StubResource("test")
-        clientNotifier = ClientNotifier(resource, configOverride=enabledConfig)
-        clientNotifier.notify()
-        self.assertEquals(notificationClient.lines, ["test"])
+        notifier = Notifier(None, id="test")
 
-        notificationClient.clear()
-        clientNotifier = clientNotifier.clone(StubResource("sub"), label="alt")
-        clientNotifier.notify()
-        self.assertEquals(notificationClient.lines, ["test", "sub"])
-
-
-class ClientNotifierTests(TestCase):
-
-    def test_clientNotifier(self):
-        enabledConfig = Config(PListConfigProvider(DEFAULT_CONFIG))
-        enabledConfig.Notifications["Enabled"] = True
-        resource = StubResource("test")
-        subResource = StubResource("sub")
-        clientNotifier = ClientNotifier(resource, configOverride=enabledConfig)
-
-        self.assertEquals(clientNotifier.ids, {"default": (resource, None)})
-        clone = clientNotifier.clone(subResource, label="alt", id="altID")
+        self.assertEquals(notifier._ids, {"default": "test"})
+        clone = notifier.clone(label="alt", id="altID")
         self.assertEquals("altID", clone.getID(label="alt"))
-        self.assertEquals(clone.ids, {
-            "default" : (resource, None),
-            "alt"     : (subResource, "altID"),
+        self.assertEquals(clone._ids, {
+            "default" : "test",
+            "alt"     : "altID",
         })
-        self.assertEquals("test", clientNotifier.getID())
-        self.assertEquals(clientNotifier.ids, {
-            "default" : (resource, "test"),
+        self.assertEquals("test", notifier.getID())
+        self.assertEquals(notifier._ids, {
+            "default" : "test",
         })
-        self.assertEquals(None, clientNotifier.getID(label="notthere"))
+        self.assertEquals(None, notifier.getID(label="notthere"))
 
-        resource = StubResource("urn:uuid:foo")
-        clientNotifier = ClientNotifier(resource, configOverride=enabledConfig)
-        self.assertEquals("foo", clientNotifier.getID())
+        notifier = Notifier(None, id="urn:uuid:foo")
+        self.assertEquals("foo", notifier.getID())
 
-        clientNotifier.disableNotify()
-        self.assertEquals(clientNotifier._notify, False)
-        clientNotifier.enableNotify(None)
-        self.assertEquals(clientNotifier._notify, True)
+        notifier.disableNotify()
+        self.assertEquals(notifier._notify, False)
+        notifier.enableNotify(None)
+        self.assertEquals(notifier._notify, True)
 
 
 class NotificationClientFactoryTests(TestCase):
@@ -145,11 +120,11 @@
         self.factory.connected = False
 
 
-class NotificationClientTests(TestCase):
+class NotifierFactoryTests(TestCase):
 
     def setUp(self):
         TestCase.setUp(self)
-        self.client = NotificationClient(None, None, reactor=Clock())
+        self.client = NotifierFactory(None, None, reactor=Clock())
         self.client.factory = StubNotificationClientFactory()
 
     def test_sendWhileNotConnected(self):

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -103,7 +103,7 @@
         path.createDirectory()
 
         # Need a data store
-        _newStore = CommonDataStore(self.site.resource.fp, True, False)
+        _newStore = CommonDataStore(self.site.resource.fp, None, True, False)
 
         self.calendarCollection = CalendarHomeProvisioningFile(
             path,
@@ -306,7 +306,7 @@
         self.createStockDirectoryService()
 
         # Need a data store
-        _newStore = CommonDataStore(fp, True, False)
+        _newStore = CommonDataStore(fp, None, True, False)
 
         self.homeProvisioner = CalendarHomeProvisioningFile(
             os.path.join(fp.path, "calendars"),
@@ -372,7 +372,7 @@
         self.createStockDirectoryService()
 
         # Need a data store
-        _newStore = CommonDataStore(fp, True, False)
+        _newStore = CommonDataStore(fp, None, True, False)
 
         self.homeProvisioner = AddressBookHomeProvisioningFile(
             os.path.join(fp.path, "addressbooks"),

Modified: CalendarServer/trunk/txcaldav/calendarstore/file.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txcaldav/calendarstore/file.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -68,8 +68,8 @@
 class CalendarHome(CommonHome):
     implements(ICalendarHome)
 
-    def __init__(self, uid, path, calendarStore, transaction):
-        super(CalendarHome, self).__init__(uid, path, calendarStore, transaction)
+    def __init__(self, uid, path, calendarStore, transaction, notifier):
+        super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifier)
 
         self._childClass = Calendar
 
@@ -123,7 +123,7 @@
     """
     implements(ICalendar)
 
-    def __init__(self, name, calendarHome, realName=None):
+    def __init__(self, name, calendarHome, notifier, realName=None):
         """
         Initialize a calendar pointing at a path on disk.
 
@@ -138,9 +138,9 @@
         will eventually have on disk.
         @type realName: C{str}
         """
+        super(Calendar, self).__init__(name, calendarHome, notifier,
+            realName=realName)
 
-        super(Calendar, self).__init__(name, calendarHome, realName)
-
         self._index = Index(self)
         self._invites = Invites(self)
         self._objectResourceClass = CalendarObject
@@ -264,6 +264,8 @@
                     self._path.remove()
             return undo
         self._transaction.addOperation(do, "set calendar component %r" % (self.name(),))
+        if self._calendar._notifier:
+            self._transaction.postCommit(self._calendar._notifier.notify)
 
     def component(self):
         if self._component is not None:

Modified: CalendarServer/trunk/txcaldav/calendarstore/test/common.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/test/common.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txcaldav/calendarstore/test/common.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -46,6 +46,7 @@
 from twext.web2.dav.element.base import WebDAVUnknownElement
 from twext.python.vcomponent import VComponent
 
+from twistedcaldav.notify import Notifier
 
 storePath = FilePath(__file__).parent().child("calendar_store")
 
@@ -381,8 +382,12 @@
                 ],
                 davxml.ResourceType.calendar) #@UndefinedVariable
         checkProperties()
+
         self.commit()
 
+        # Make sure notification fired after commit
+        self.assertTrue(self.notifierFactory.compare([("update", "home1")]))
+
         # Make sure it's available in a new transaction; i.e. test the commit.
         home = self.homeUnderTest()
         self.assertNotIdentical(home.calendarWithName(name), None)
@@ -412,13 +417,20 @@
         exists.
         """
         home = self.homeUnderTest()
+
         # FIXME: test transactions
         for name in home1_calendarNames:
             self.assertNotIdentical(home.calendarWithName(name), None)
             home.removeCalendarWithName(name)
             self.assertEquals(home.calendarWithName(name), None)
 
+        self.commit()
 
+        # Make sure notification fired after commit
+        self.assertTrue(self.notifierFactory.compare(
+            [("update", "home1"), ("update", "home1"), ("update", "home1")]))
+
+
     def test_removeCalendarWithName_absent(self):
         """
         Attempt to remove an non-existing calendar should raise.
@@ -513,6 +525,20 @@
                 None
             )
 
+        # Make sure notifications are fired after commit
+        self.commit()
+        self.assertTrue(
+            self.notifierFactory.compare(
+                [
+                    ("update", "home1"),
+                    ("update", "calendar_1"),
+                    ("update", "home1"),
+                    ("update", "calendar_1"),
+                    ("update", "home1"),
+                    ("update", "calendar_1"),
+                ]
+            )
+        )
 
     def test_removeCalendarObjectWithName_exists(self):
         """
@@ -660,7 +686,19 @@
         calendarObject = calendar1.calendarObjectWithName(name)
         self.assertEquals(calendarObject.component(), component)
 
+        self.commit()
 
+        # Make sure notifications fire after commit
+        self.assertTrue(
+            self.notifierFactory.compare(
+                [
+                    ("update", "home1"),
+                    ("update", "calendar_1"),
+                ]
+            )
+        )
+
+
     def test_createCalendarObjectWithName_exists(self):
         """
         L{ICalendar.createCalendarObjectWithName} raises
@@ -754,7 +792,19 @@
         calendarObject = calendar1.calendarObjectWithName("1.ics")
         self.assertEquals(calendarObject.component(), component)
 
+        self.commit()
 
+        # Make sure notification fired after commit
+        self.assertTrue(
+            self.notifierFactory.compare(
+                [
+                    ("update", "home1"),
+                    ("update", "calendar_1"),
+                ]
+            )
+        )
+
+
     def checkPropertiesMethod(self, thunk):
         """
         Verify that the given object has a properties method that returns an
@@ -1025,3 +1075,22 @@
         self.assertRaises(AlreadyFinishedError, txn.abort)
 
 
+
+class StubNotifierFactory(object):
+
+    """ For testing push notifications without an XMPP server """
+
+    def __init__(self):
+        self.reset()
+
+    def newNotifier(self, label="default", id=None):
+        return Notifier(self, label=label, id=id)
+
+    def send(self, op, id):
+        self._history.append((op, id))
+
+    def reset(self):
+        self._history = []
+
+    def compare(self, expected):
+        return self._history == expected

Modified: CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txcaldav/calendarstore/test/test_file.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -33,7 +33,7 @@
 from txcaldav.calendarstore.file import Calendar, CalendarObject
 
 from txcaldav.calendarstore.test.common import (
-    CommonTests, event4_text, event1modified_text)
+    CommonTests, event4_text, event1modified_text, StubNotifierFactory)
 
 storePath = FilePath(__file__).parent().child("calendar_store")
 
@@ -48,6 +48,7 @@
 todo = lambda why: lambda f: _todo(f, why)
 
 
+
 def setUpCalendarStore(test):
     test.root = FilePath(test.mktemp())
     test.root.createDirectory()
@@ -57,7 +58,8 @@
     calendarPath.parent().makedirs()
     storePath.copyTo(calendarPath)
 
-    test.calendarStore = CalendarStore(storeRootPath)
+    test.notifierFactory = StubNotifierFactory()
+    test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory)
     test.txn = test.calendarStore.newTransaction()
     assert test.calendarStore is not None, "No calendar store?"
 

Modified: CalendarServer/trunk/txcarddav/addressbookstore/file.py
===================================================================
--- CalendarServer/trunk/txcarddav/addressbookstore/file.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txcarddav/addressbookstore/file.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -58,8 +58,8 @@
 
     implements(IAddressBookHome)
 
-    def __init__(self, uid, path, addressbookStore, transaction):
-        super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction)
+    def __init__(self, uid, path, addressbookStore, transaction, notifier):
+        super(AddressBookHome, self).__init__(uid, path, addressbookStore, transaction, notifier)
 
         self._childClass = AddressBook
 
@@ -81,7 +81,7 @@
     """
     implements(IAddressBook)
 
-    def __init__(self, name, addressbookHome, realName=None):
+    def __init__(self, name, addressbookHome, notifier, realName=None):
         """
         Initialize an addressbook pointing at a path on disk.
 
@@ -97,7 +97,8 @@
         @type realName: C{str}
         """
         
-        super(AddressBook, self).__init__(name, addressbookHome, realName)
+        super(AddressBook, self).__init__(name, addressbookHome, notifier,
+            realName=realName)
 
         self._index = Index(self)
         self._invites = Invites(self)
@@ -204,8 +205,11 @@
                     self._path.remove()
             return undo
         self._transaction.addOperation(do, "set addressbook component %r" % (self.name(),))
+        if self._addressbook._notifier:
+            self._transaction.postCommit(self._addressbook._notifier.notify)
 
 
+
     def component(self):
         if self._component is not None:
             return self._component

Modified: CalendarServer/trunk/txcarddav/addressbookstore/test/common.py
===================================================================
--- CalendarServer/trunk/txcarddav/addressbookstore/test/common.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txcarddav/addressbookstore/test/common.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -39,6 +39,7 @@
     IAddressBook, IAddressBookTransaction
 )
 from twistedcaldav.vcard import Component as VComponent
+from twistedcaldav.notify import Notifier
 
 from twext.python.filepath import CachingFilePath as FilePath
 from twext.web2.dav import davxml
@@ -337,6 +338,9 @@
         checkProperties()
         self.commit()
 
+        # Make sure notification fired after commit
+        self.assertTrue(self.notifierFactory.compare([("update", "home1")]))
+
         # Make sure it's available in a new transaction; i.e. test the commit.
         home = self.homeUnderTest()
         self.assertNotIdentical(home.addressbookWithName(name), None)
@@ -372,7 +376,13 @@
             home.removeAddressBookWithName(name)
             self.assertEquals(home.addressbookWithName(name), None)
 
+        self.commit()
 
+        # Make sure notification fired after commit
+        self.assertTrue(self.notifierFactory.compare(
+            [("update", "home1"), ("update", "home1"), ("update", "home1")]))
+
+
     def test_removeAddressBookWithName_absent(self):
         """
         Attempt to remove an non-existing addressbook should raise.
@@ -482,7 +492,22 @@
                 addressbook.addressbookObjectWithName(name), None
             )
 
+        # Make sure notifications are fired after commit
+        self.commit()
+        self.assertTrue(
+            self.notifierFactory.compare(
+                [
+                    ("update", "home1"),
+                    ("update", "addressbook_1"),
+                    ("update", "home1"),
+                    ("update", "addressbook_1"),
+                    ("update", "home1"),
+                    ("update", "addressbook_1"),
+                ]
+            )
+        )
 
+
     def test_removeAddressBookObjectWithName_absent(self):
         """
         Attempt to remove an non-existing addressbook object should raise.
@@ -601,7 +626,19 @@
         addressbookObject = addressbook1.addressbookObjectWithName(name)
         self.assertEquals(addressbookObject.component(), component)
 
+        self.commit()
 
+        # Make sure notifications fire after commit
+        self.assertTrue(
+            self.notifierFactory.compare(
+                [
+                    ("update", "home1"),
+                    ("update", "addressbook_1"),
+                ]
+            )
+        )
+
+
     def test_createAddressBookObjectWithName_exists(self):
         """
         L{IAddressBook.createAddressBookObjectWithName} raises
@@ -695,7 +732,18 @@
         addressbookObject = addressbook1.addressbookObjectWithName("1.vcf")
         self.assertEquals(addressbookObject.component(), component)
 
+        self.commit()
 
+        # Make sure notification fired after commit
+        self.assertTrue(
+            self.notifierFactory.compare(
+                [
+                    ("update", "home1"),
+                    ("update", "addressbook_1"),
+                ]
+            )
+        )
+
     def checkPropertiesMethod(self, thunk):
         """
         Verify that the given object has a properties method that returns an
@@ -780,3 +828,23 @@
         )
 
 
+
+
+class StubNotifierFactory(object):
+
+    """ For testing push notifications without an XMPP server """
+
+    def __init__(self):
+        self.reset()
+
+    def newNotifier(self, label="default", id=None):
+        return Notifier(self, label=label, id=id)
+
+    def send(self, op, id):
+        self._history.append((op, id))
+
+    def reset(self):
+        self._history = []
+
+    def compare(self, expected):
+        return self._history == expected

Modified: CalendarServer/trunk/txcarddav/addressbookstore/test/test_file.py
===================================================================
--- CalendarServer/trunk/txcarddav/addressbookstore/test/test_file.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txcarddav/addressbookstore/test/test_file.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -33,7 +33,7 @@
 from txcarddav.addressbookstore.file import AddressBook, AddressBookObject
 
 from txcarddav.addressbookstore.test.common import (
-    CommonTests, vcard4_text, vcard1modified_text)
+    CommonTests, vcard4_text, vcard1modified_text, StubNotifierFactory)
 
 storePath = FilePath(__file__).parent().child("addressbook_store")
 
@@ -57,7 +57,8 @@
     addressbookPath.parent().makedirs()
     storePath.copyTo(addressbookPath)
 
-    test.addressbookStore = AddressBookStore(storeRootPath)
+    test.notifierFactory = StubNotifierFactory()
+    test.addressbookStore = AddressBookStore(storeRootPath, test.notifierFactory)
     test.txn = test.addressbookStore.newTransaction()
     assert test.addressbookStore is not None, "No addressbook store?"
 

Modified: CalendarServer/trunk/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/file.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txdav/common/datastore/file.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -64,7 +64,8 @@
     """
     implements(IDataStore)
 
-    def __init__(self, path, enableCalendars=True, enableAddressBooks=True):
+    def __init__(self, path, notifierFactory, enableCalendars=True,
+        enableAddressBooks=True):
         """
         Create a store.
 
@@ -75,6 +76,7 @@
         super(CommonDataStore, self).__init__(path)
         self.enableCalendars = enableCalendars
         self.enableAddressBooks = enableAddressBooks
+        self._notifierFactory = notifierFactory
         self._transactionClass = CommonStoreTransaction
 
     def newTransaction(self, name='no name'):
@@ -83,7 +85,7 @@
 
         @see Transaction
         """
-        return self._transactionClass(self, name, self.enableCalendars, self.enableAddressBooks)
+        return self._transactionClass(self, name, self.enableCalendars, self.enableAddressBooks, self._notifierFactory)
 
 class CommonStoreTransaction(DataStoreTransaction):
     """
@@ -95,7 +97,8 @@
 
     _homeClass = {}
 
-    def __init__(self, dataStore, name, enableCalendars, enableAddressBooks):
+    def __init__(self, dataStore, name, enableCalendars, enableAddressBooks,
+        notifierFactory):
         """
         Initialize a transaction; do not call this directly, instead call
         L{DataStore.newTransaction}.
@@ -114,6 +117,7 @@
         self._homes[ECALENDARTYPE] = {}
         self._homes[EADDRESSBOOKTYPE] = {}
         self._notifications = {}
+        self._notifierFactory = notifierFactory
 
         extraInterfaces = []
         if enableCalendars:
@@ -130,10 +134,10 @@
 
 
     def calendarHomeWithUID(self, uid, create=False):
-        return self.homeWithUID(ECALENDARTYPE, uid, create)
+        return self.homeWithUID(ECALENDARTYPE, uid, create=create)
 
     def addressbookHomeWithUID(self, uid, create=False):
-        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create)
+        return self.homeWithUID(EADDRESSBOOKTYPE, uid, create=create)
 
     def homeWithUID(self, storeType, uid, create=False):
         if (uid, self) in self._homes[storeType]:
@@ -189,7 +193,13 @@
         else:
             homePath = childPath
 
-        home = self._homeClass[storeType](uid, homePath, self._dataStore, self)
+        if self._notifierFactory:
+            notifier = self._notifierFactory.newNotifier(id=uid)
+        else:
+            notifier = None
+
+        home = self._homeClass[storeType](uid, homePath, self._dataStore, self,
+            notifier)
         self._homes[storeType][(uid, self)] = home
         if creating:
             home.created()
@@ -259,11 +269,12 @@
 
     _childClass = None
 
-    def __init__(self, uid, path, dataStore, transaction):
+    def __init__(self, uid, path, dataStore, transaction, notifier):
         self._dataStore = dataStore
         self._uid = self._peruser_uid = uid
         self._path = path
         self._transaction = transaction
+        self._notifier = notifier
         self._shares = SharedCollectionsDatabase(StubResource(self))
         self._newChildren = {}
         self._removedChildren = set()
@@ -312,7 +323,11 @@
 
         childPath = self._path.child(name)
         if childPath.isdir():
-            existingChild = self._childClass(name, self)
+            if self._notifier:
+                notifier = self._notifier.clone(label="collection", id=name)
+            else:
+                notifier = None
+            existingChild = self._childClass(name, self, notifier)
             self._cachedChildren[name] = existingChild
             return existingChild
         else:
@@ -336,7 +351,11 @@
 
         # FIXME: some way to roll this back.
 
-        c = self._newChildren[name] = self._childClass(temporary.basename(), self, name)
+        if self._notifier:
+            notifier = self._notifier.clone(label="collection", id=name)
+        else:
+            notifier = None
+        c = self._newChildren[name] = self._childClass(temporary.basename(), self, notifier, realName=name)
         c.retrieveOldIndex().create()
         def do():
             try:
@@ -354,6 +373,8 @@
             return lambda: childPath.remove()
 
         self._transaction.addOperation(do, "create child %r" % (name,))
+        if self._notifier:
+            self._transaction.postCommit(self._notifier.notify)
         props = c.properties()
         props[PropertyName(*ResourceType.qname())] = c.resourceType()
         self.createdChild(c)
@@ -393,7 +414,6 @@
                     self.log_error("Unable to delete trashed child at %s: %s" % (trash.fp, e))
 
             transaction.addOperation(cleanup, "remove child backup %r" % (name,))
-
             def undo():
                 trash.moveTo(childPath)
 
@@ -404,6 +424,10 @@
             do, "prepare child remove %r" % (name,)
         )
 
+        if self._notifier:
+            self._transaction.postCommit(self._notifier.notify)
+
+
     # @cached
     def properties(self):
         # FIXME: needs tests for actual functionality
@@ -423,7 +447,7 @@
 
     _objectResourceClass = None
 
-    def __init__(self, name, home, realName=None):
+    def __init__(self, name, home, notifier, realName=None):
         """
         Initialize an home child pointing at a path on disk.
 
@@ -440,6 +464,7 @@
         """
         self._name = name
         self._home = home
+        self._notifier = notifier
         self._peruser_uid = home._peruser_uid
         self._transaction = home._transaction
         self._newObjectResources = {}
@@ -496,6 +521,8 @@
         self._transaction.addOperation(doIt, "rename home child %r -> %r" %
                                        (oldName, name))
 
+        if self._notifier:
+            self._transaction.postCommit(self._notifier.notify)
 
     def ownerHome(self):
         return self._home
@@ -560,7 +587,10 @@
         objectResource.setComponent(component)
         self._cachedObjectResources[name] = objectResource
 
+        # Note: setComponent triggers a notification, so we don't need to
+        # call notify( ) here like we do for object removal.
 
+
     @writeOperation
     def removeObjectResourceWithName(self, name):
         if name.startswith("."):
@@ -578,6 +608,8 @@
                 return lambda: None
             self._transaction.addOperation(do, "remove object resource object %r" %
                                            (name,))
+            if self._notifier:
+                self._transaction.postCommit(self._notifier.notify)
         else:
             raise NoSuchObjectResourceError(name)
 

Modified: CalendarServer/trunk/txdav/datastore/file.py
===================================================================
--- CalendarServer/trunk/txdav/datastore/file.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txdav/datastore/file.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -148,6 +148,7 @@
         self._dataStore = dataStore
         self._termination = None
         self._operations = []
+        self._postCommitOperations = []
         self._tracker = _CommitTracker(name)
 
 
@@ -202,4 +203,8 @@
                         self.log_error("Cannot undo DataStoreTransaction")
                 raise
 
+        for operation in self._postCommitOperations:
+            operation()
 
+    def postCommit(self, operation):
+        self._postCommitOperations.append(operation)

Modified: CalendarServer/trunk/txdav/idav.py
===================================================================
--- CalendarServer/trunk/txdav/idav.py	2010-07-30 22:52:49 UTC (rev 5963)
+++ CalendarServer/trunk/txdav/idav.py	2010-08-02 17:53:35 UTC (rev 5964)
@@ -142,3 +142,15 @@
         @raise AlreadyFinishedError: The transaction was already finished with
             an 'abort' or 'commit' and cannot be committed again.
         """
+
+
+    def postCommit(operation):
+        """
+        Registers an operation to be executed after the transaction is
+        committed.
+
+        postCommit can be called multiple times, and operations are executed
+        in the order which they were registered.
+
+        @param operation: a callable.
+        """
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20100802/589d1daa/attachment-0001.html>


More information about the calendarserver-changes mailing list