[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