[CalendarServer-changes] [14401] CalendarServer/trunk/twistedcaldav
source_changes at macosforge.org
source_changes at macosforge.org
Tue Feb 10 14:49:34 PST 2015
Revision: 14401
http://trac.calendarserver.org//changeset/14401
Author: sagen at apple.com
Date: 2015-02-10 14:49:34 -0800 (Tue, 10 Feb 2015)
Log Message:
-----------
Sync token can optionally include a component derived from configuration, especially so that APNS push topic changes can be detected by clients
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/config.py
CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
CalendarServer/trunk/twistedcaldav/resource.py
CalendarServer/trunk/twistedcaldav/stdconfig.py
CalendarServer/trunk/twistedcaldav/test/test_config.py
Modified: CalendarServer/trunk/twistedcaldav/config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/config.py 2015-02-10 21:54:08 UTC (rev 14400)
+++ CalendarServer/trunk/twistedcaldav/config.py 2015-02-10 22:49:34 UTC (rev 14401)
@@ -22,8 +22,9 @@
"config",
]
+import copy
+import hashlib
import os
-import copy
class ConfigurationError(RuntimeError):
"""
@@ -262,6 +263,7 @@
self._updating = False
self._dirty = False
+ self._cachedSyncToken = None
def load(self, configFile):
@@ -297,9 +299,78 @@
def reset(self):
self._data = ConfigDict(copy.deepcopy(self._provider.getDefaults()))
self._dirty = True
+ self._syncTokenKeys = []
+ self._cachedSyncToken = None
+ def getKeyPath(self, keyPath):
+ """
+ Allows the getting of arbitrary nested dictionary keys via a single
+ dot-separated string. For example, getKeyPath(self, "foo.bar.baz")
+ would fetch parent["foo"]["bar"]["baz"]. If any of the keys don't
+ exist, None is returned instead.
+
+ @param keyPath: a dot-delimited string specifying the path of keys to
+ traverse
+ @type keyPath: C{str}
+ @return: the value at keyPath
+ """
+ parent = self
+ parts = keyPath.split(".")
+ for part in parts[:-1]:
+ child = parent.get(part, None)
+ if child is None:
+ return None
+ parent = child
+ return parent.get(parts[-1], None)
+
+
+ def addSyncTokenKey(self, keyPath):
+ """
+ Indicates the specified key should be taken into account when generating
+ the sync token. Also invalidates the (possibly) cached syncToken.
+
+ @param keyPath: a dot-delimited string specifying the path of keys to
+ traverse
+ @type keyPath: C{str}
+ """
+ if keyPath not in self._syncTokenKeys:
+ self._syncTokenKeys.append(keyPath)
+ self._cachedSyncToken = None
+
+
+ def syncToken(self):
+ """
+ Iterates the previously registered keys (sorted, so the order in which
+ the keys were registered doesn't affect the hash) and generates an MD5
+ hash of the combined values. The hash is cached, and is invalidated
+ during a reload or if invalidateSyncToken is called.o
+
+ @return: the sync token
+ @rtype: C{str}
+ """
+ if self._cachedSyncToken is None:
+ pieces = []
+ self._syncTokenKeys.sort()
+ for key in self._syncTokenKeys:
+ value = self.getKeyPath(key)
+ if value is None:
+ value = ""
+ pieces.append(key + ":" + str(value))
+ whole = "|".join(pieces)
+ self._cachedSyncToken = hashlib.md5(whole).hexdigest()
+ return self._cachedSyncToken
+
+
+ def invalidateSyncToken(self):
+ """
+ Invalidates the cached copy of the sync token.
+ """
+ self._cachedSyncToken = None
+
+
+
def mergeData(oldData, newData):
"""
Merge two ConfigDict objects; oldData will be updated with all the keys
Modified: CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2015-02-10 21:54:08 UTC (rev 14400)
+++ CalendarServer/trunk/twistedcaldav/method/report_sync_collection.py 2015-02-10 22:49:34 UTC (rev 14401)
@@ -87,7 +87,7 @@
# the child resource loop and supply those to the checkPrivileges on each child.
filteredaces = (yield self.inheritedACEsforChildren(request))
- changed, removed, notallowed, newtoken = yield self.whatchanged(sync_collection.sync_token, depth)
+ changed, removed, notallowed, newtoken, resourceChanged = yield self.whatchanged(sync_collection.sync_token, depth)
# Now determine which valid resources are readable and which are not
ok_resources = []
@@ -105,6 +105,9 @@
inherited_aces=filteredaces
)
+ if resourceChanged:
+ ok_resources.append((self, request.uri))
+
for child, child_uri in ok_resources:
href = element.HRef.fromString(child_uri)
try:
Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py 2015-02-10 21:54:08 UTC (rev 14400)
+++ CalendarServer/trunk/twistedcaldav/resource.py 2015-02-10 22:49:34 UTC (rev 14401)
@@ -1183,15 +1183,25 @@
@inlineCallbacks
def whatchanged(self, client_token, depth):
+
+ client_data_token = None
+ client_config_token = None
+
+ if client_token:
+ if "/" in client_token:
+ client_data_token, client_config_token = client_token.split("/")
+ else:
+ client_data_token = client_token
+
current_token = (yield self.getSyncToken())
current_uuid, current_revision = current_token[6:].split("_", 1)
current_revision = int(current_revision)
- if client_token:
+ if client_data_token:
try:
- if not client_token.startswith("data:,"):
+ if not client_data_token.startswith("data:,"):
raise ValueError
- caluuid, revision = client_token[6:].split("_", 1)
+ caluuid, revision = client_data_token[6:].split("_", 1)
revision = int(revision)
# Check client token validity
@@ -1217,9 +1227,19 @@
"Sync token not recognized",
))
- returnValue((changed, removed, notallowed, current_token))
+ if config.EnableConfigSyncToken:
+ # Append the app-level portion of sync token (e.g. derived from config)
+ newConfigToken = config.syncToken()
+ current_token = "{}/{}".format(current_token, newConfigToken)
+ # If the config token changed, note that in the returned tuple
+ resourceChanged = (newConfigToken != client_config_token)
+ else:
+ resourceChanged = False
+ returnValue((changed, removed, notallowed, current_token, resourceChanged))
+
+
def _indexWhatChanged(self, revision, depth):
# Now handled directly by newstore
raise NotImplementedError
Modified: CalendarServer/trunk/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/stdconfig.py 2015-02-10 21:54:08 UTC (rev 14400)
+++ CalendarServer/trunk/twistedcaldav/stdconfig.py 2015-02-10 22:49:34 UTC (rev 14401)
@@ -475,6 +475,7 @@
"EnableAddMember" : True, # POST ;add-member extension
"EnableSyncReport" : True, # REPORT collection-sync
"EnableSyncReportHome" : True, # REPORT collection-sync on home collections
+ "EnableConfigSyncToken" : False, # Sync token includes config component
"EnableWellKnown" : True, # /.well-known resource
"EnableCalendarQueryExtended" : True, # Extended calendar-query REPORT
@@ -1554,6 +1555,12 @@
def _updateNotifications(configDict, reloading=False):
+
+ # These three keys go into producing the config sync token
+ config.addSyncTokenKey("Notifications.Services.APNS.Enabled")
+ config.addSyncTokenKey("Notifications.Services.APNS.CalDAV.Topic")
+ config.addSyncTokenKey("Notifications.Services.APNS.CardDAV.Topic")
+
# Reloading not supported -- requires process running as root
if reloading:
return
Modified: CalendarServer/trunk/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_config.py 2015-02-10 21:54:08 UTC (rev 14400)
+++ CalendarServer/trunk/twistedcaldav/test/test_config.py 2015-02-10 22:49:34 UTC (rev 14401)
@@ -66,6 +66,60 @@
</dict>
</dict>
+ <key>Notifications</key>
+ <dict>
+
+ <key>Services</key>
+ <dict>
+
+ <key>AMP</key>
+ <dict>
+ <key>Enabled</key>
+ <true/>
+ <key>Port</key>
+ <integer>62311</integer>
+ <key>EnableStaggering</key>
+ <false/>
+ <key>StaggerSeconds</key>
+ <integer>3</integer>
+ </dict>
+
+ <key>APNS</key>
+ <dict>
+ <key>CalDAV</key>
+ <dict>
+ <key>AuthorityChainPath</key>
+ <string>com.apple.calendar.chain.pem</string>
+ <key>CertificatePath</key>
+ <string>com.apple.calendar.cert.pem</string>
+ <key>PrivateKeyPath</key>
+ <string>com.apple.calendar.key.pem</string>
+ <key>Topic</key>
+ <string>calendar-topic</string>
+ <key>Passphrase</key>
+ <string>password</string>
+ </dict>
+ <key>CardDAV</key>
+ <dict>
+ <key>AuthorityChainPath</key>
+ <string>com.apple.contact.chain.pem</string>
+ <key>CertificatePath</key>
+ <string>com.apple.contact.cert.pem</string>
+ <key>PrivateKeyPath</key>
+ <string>com.apple.contact.key.pem</string>
+ <key>Topic</key>
+ <string>contact-topic</string>
+ <key>Passphrase</key>
+ <string>password</string>
+ </dict>
+ <key>Enabled</key>
+ <true/>
+ </dict>
+
+ </dict>
+
+ </dict>
+
</dict>
</plist>
"""
@@ -635,3 +689,35 @@
config.load(self.testMaster)
self.assertEquals(config.HTTPPort, 9008)
self.assertEquals(config.SSLPort, 8443)
+
+
+ def testSyncToken(self):
+ config.load(self.testConfig)
+
+ # no sync token keys specified; need to empty this array here because
+ # stdconfig is registering keys automatically
+ config._syncTokenKeys = []
+ self.assertEquals("d41d8cd98f00b204e9800998ecf8427e", config.syncToken())
+
+ # add sync token keys (some with multiple levels)
+ config.addSyncTokenKey("DefaultLogLevel")
+ config.addSyncTokenKey("Notifications.Services.APNS.Enabled")
+ config.addSyncTokenKey("Notifications.Services.APNS.CalDAV.Topic")
+ config.addSyncTokenKey("Notifications.Services.APNS.CardDAV.Topic")
+ self.assertEquals("7473205187d7a6ff0c61a2b6b04053c5", config.syncToken())
+
+ # modify a sync token key value
+ config.Notifications.Services.APNS.CalDAV.Topic = "changed"
+ # direct manipulation of config requires explicit invalidation
+ self.assertEquals("7473205187d7a6ff0c61a2b6b04053c5", config.syncToken())
+ config.invalidateSyncToken()
+ self.assertEquals("4cdbb3841625d001dc768439f5a88cba", config.syncToken())
+
+ # add a non existent key (not an error because it could exist later)
+ config.addSyncTokenKey("Notifications.Services.APNS.CalDAV.NonExistent")
+ config.invalidateSyncToken()
+ self.assertEquals("2ffb128cee5a4b217cef82fd31ae7767", config.syncToken())
+
+ # reload automatically invalidates
+ config.reload()
+ self.assertEquals("a1c46c5aff1899658dac033ee8520b07", config.syncToken())
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150210/160e9e1d/attachment-0001.html>
More information about the calendarserver-changes
mailing list