[CalendarServer-changes] [11934] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Mar 12 11:16:18 PDT 2014


Revision: 11934
          http://trac.calendarserver.org//changeset/11934
Author:   sagen at apple.com
Date:     2013-11-12 13:38:00 -0800 (Tue, 12 Nov 2013)
Log Message:
-----------
New APNS protocol with support for priority levels

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/push/amppush.py
    CalendarServer/trunk/calendarserver/push/applepush.py
    CalendarServer/trunk/calendarserver/push/notifier.py
    CalendarServer/trunk/calendarserver/push/test/test_amppush.py
    CalendarServer/trunk/calendarserver/push/test/test_applepush.py
    CalendarServer/trunk/calendarserver/push/test/test_notifier.py
    CalendarServer/trunk/calendarserver/push/util.py
    CalendarServer/trunk/calendarserver/tools/ampnotifications.py
    CalendarServer/trunk/calendarserver/tools/gateway.py
    CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/caldav/datastore/test/common.py
    CalendarServer/trunk/txdav/carddav/datastore/test/common.py
    CalendarServer/trunk/txdav/common/datastore/sql.py
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
    CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
    CalendarServer/trunk/txdav/common/datastore/test/util.py
    CalendarServer/trunk/txdav/idav.py

Modified: CalendarServer/trunk/calendarserver/push/amppush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/amppush.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/amppush.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -23,7 +23,9 @@
 import time
 import uuid
 
+from calendarserver.push.util import PushPriority
 
+
 log = Logger()
 
 
@@ -49,7 +51,8 @@
 
 class NotificationForID(amp.Command):
     arguments = [('id', amp.String()),
-                 ('dataChangedTimestamp', amp.Integer(optional=True))]
+                 ('dataChangedTimestamp', amp.Integer(optional=True)),
+                 ('priority', amp.Integer(optional=True))]
     response = [('status', amp.String())]
 
 
@@ -82,12 +85,14 @@
 
 
     @inlineCallbacks
-    def enqueue(self, transaction, id, dataChangedTimestamp=None):
+    def enqueue(self, transaction, id, dataChangedTimestamp=None,
+        priority=PushPriority.high):
         if dataChangedTimestamp is None:
             dataChangedTimestamp = int(time.time())
         for protocol in self.protocols:
             yield protocol.callRemote(NotificationForID, id=id,
-                dataChangedTimestamp=dataChangedTimestamp)
+                dataChangedTimestamp=dataChangedTimestamp,
+                priority=priority.value)
 
 
 
@@ -103,10 +108,12 @@
 
 
     @NotificationForID.responder
-    def enqueueFromWorker(self, id, dataChangedTimestamp=None):
+    def enqueueFromWorker(self, id, dataChangedTimestamp=None,
+        priority=PushPriority.high.value):
         if dataChangedTimestamp is None:
             dataChangedTimestamp = int(time.time())
-        self.master.enqueue(None, id, dataChangedTimestamp=dataChangedTimestamp)
+        self.master.enqueue(None, id, dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.lookupByValue(priority))
         return {"status" : "OK"}
 
 
@@ -167,7 +174,8 @@
         self.subscribers.remove(p)
 
 
-    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None):
+    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None,
+        priority=PushPriority.high):
         """
         Sends an AMP push notification to any clients subscribing to this pushKey.
 
@@ -192,23 +200,26 @@
             if token is not None:
                 tokens.append(token)
         if tokens:
-            return self.scheduleNotifications(tokens, pushKey, dataChangedTimestamp)
+            return self.scheduleNotifications(tokens, pushKey,
+                dataChangedTimestamp, priority)
 
 
     @inlineCallbacks
-    def sendNotification(self, token, id, dataChangedTimestamp):
+    def sendNotification(self, token, id, dataChangedTimestamp, priority):
         for subscriber in self.subscribers:
             if subscriber.subscribedToID(id):
-                yield subscriber.notify(token, id, dataChangedTimestamp)
+                yield subscriber.notify(token, id, dataChangedTimestamp,
+                    priority)
 
 
     @inlineCallbacks
-    def scheduleNotifications(self, tokens, id, dataChangedTimestamp):
+    def scheduleNotifications(self, tokens, id, dataChangedTimestamp, priority):
         if self.scheduler is not None:
-            self.scheduler.schedule(tokens, id, dataChangedTimestamp)
+            self.scheduler.schedule(tokens, id, dataChangedTimestamp, priority)
         else:
             for token in tokens:
-                yield self.sendNotification(token, id, dataChangedTimestamp)
+                yield self.sendNotification(token, id, dataChangedTimestamp,
+                    priority)
 
 
 
@@ -238,11 +249,12 @@
         return {"status" : "OK"}
     UnsubscribeFromID.responder(unsubscribe)
 
-    def notify(self, token, id, dataChangedTimestamp):
+    def notify(self, token, id, dataChangedTimestamp, priority):
         if self.subscribedToID(id) == token:
             self.log.debug("Sending notification for %s to %s" % (id, token))
             return self.callRemote(NotificationForID, id=id,
-                dataChangedTimestamp=dataChangedTimestamp)
+                dataChangedTimestamp=dataChangedTimestamp,
+                priority=priority.value)
 
 
     def subscribedToID(self, id):
@@ -288,8 +300,8 @@
 
 
     @inlineCallbacks
-    def notificationForID(self, id, dataChangedTimestamp):
-        yield self.callback(id, dataChangedTimestamp)
+    def notificationForID(self, id, dataChangedTimestamp, priority):
+        yield self.callback(id, dataChangedTimestamp, PushPriority.lookupByValue(priority))
         returnValue({"status" : "OK"})
 
     NotificationForID.responder(notificationForID)

Modified: CalendarServer/trunk/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/applepush.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/applepush.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -36,16 +36,27 @@
 import struct
 import time
 from txdav.common.icommondatastore import InvalidSubscriptionValues
-
-from calendarserver.push.util import validToken, TokenHistory, PushScheduler
-
+from calendarserver.push.util import (
+    validToken, TokenHistory, PushScheduler, PushPriority
+)
 from twext.internet.adaptendpoint import connect
 from twext.internet.gaiendpoint import GAIEndpoint
+from twisted.python.constants import Values, ValueConstant
 
 log = Logger()
 
 
 
+class ApplePushPriority(Values):
+    """
+    Maps calendarserver.push.util.PushPriority values to APNS-specific values
+    """
+    low    = ValueConstant(PushPriority.low.value)
+    medium = ValueConstant(PushPriority.medium.value)
+    high   = ValueConstant(PushPriority.high.value)
+
+
+
 class ApplePushNotifierService(service.MultiService):
     """
     ApplePushNotifierService is a MultiService responsible for
@@ -55,7 +66,7 @@
 
     The Apple Push Notification protocol is described here:
 
-    http://developer.apple.com/library/ios/#documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingWIthAPS/CommunicatingWIthAPS.html
+    https://developer.apple.com/library/ios/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/CommunicatingWIthAPS.html
     """
     log = Logger()
 
@@ -177,7 +188,8 @@
 
 
     @inlineCallbacks
-    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None):
+    def enqueue(self, transaction, pushKey, dataChangedTimestamp=None,
+        priority=PushPriority.high):
         """
         Sends an Apple Push Notification to any device token subscribed to
         this pushKey.
@@ -191,6 +203,8 @@
         @param dataChangedTimestamp: Timestamp (epoch seconds) for the data change
             which triggered this notification (Only used for unit tests)
         @type key: C{int}
+        @param priority: the priority level
+        @type priority: L{PushPriority}
         """
 
         try:
@@ -219,7 +233,8 @@
                     if token and uid:
                         tokens.append(token)
                 if tokens:
-                    provider.scheduleNotifications(tokens, pushKey, dataChangedTimestamp)
+                    provider.scheduleNotifications(tokens, pushKey,
+                        dataChangedTimestamp, priority)
 
 
 
@@ -230,8 +245,7 @@
     log = Logger()
 
     # Sent by provider
-    COMMAND_SIMPLE = 0
-    COMMAND_ENHANCED = 1
+    COMMAND_PROVIDER = 2
 
     # Received by provider
     COMMAND_ERROR = 8
@@ -333,7 +347,7 @@
                 yield txn.commit()
 
 
-    def sendNotification(self, token, key, dataChangedTimestamp):
+    def sendNotification(self, token, key, dataChangedTimestamp, priority):
         """
         Sends a push notification message for the key to the device associated
         with the token.
@@ -357,6 +371,7 @@
             return
 
         identifier = self.history.add(token)
+        apnsPriority = ApplePushPriority.lookupByValue(priority.value).value
         payload = json.dumps(
             {
                 "key" : key,
@@ -365,23 +380,79 @@
             }
         )
         payloadLength = len(payload)
-        self.log.debug("Sending APNS notification to {token}: id={id} payload={payload}",
-            token=token, id=identifier, payload=payload)
+        self.log.debug("Sending APNS notification to {token}: id={id} payload={payload} priority={priority}",
+            token=token, id=identifier, payload=payload, priority=apnsPriority)
 
+        """
+        Notification format
+
+        Top level:  Command (1 byte), Frame length (4 bytes), Frame data (variable)
+        Within Frame data:  Item ...
+        Item: Item number (1 byte), Item data length (2 bytes), Item data (variable)
+        Item 1: Device token (32 bytes)
+        Item 2: Payload (variable length) in JSON format, not null-terminated
+        Item 3: Notification ID (4 bytes) an opaque value used for reporting errors
+        Item 4: Expiration date (4 bytes) UNIX epoch in secondcs UTC
+        Item 5: Priority (1 byte): 10 (push sent immediately) or 5 (push sent
+            at a time that conservces power on the device receiving it)
+        """
+
+                                                    # Frame struct.pack format
+                                                    # ! Network byte order
+        command = self.COMMAND_PROVIDER             # B
+        frameLength = (                             # I
+            # Item 1 (Device token)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            32 + # device token                     # 32s
+            # Item 2 (Payload)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            payloadLength + # the JSON payload      # %d s
+            # Item 3 (Notification ID)
+            1 +  # Item number                      # B    
+            2 +  # Item length                      # H
+            4 +  # Notification ID                  # I
+            # Item 4 (Expiration)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            4 +  # Expiration seconds since epoch   # I
+            # Item 5 (Priority)
+            1 +  # Item number                      # B
+            2 +  # Item length                      # H
+            1    # Priority                         # B
+        )
+
         self.transport.write(
-            struct.pack("!BIIH32sH%ds" % (payloadLength,),
-                self.COMMAND_ENHANCED,           # Command
-                identifier,                      # Identifier
-                int(time.time()) + 72 * 60 * 60, # Expires in 72 hours
+            struct.pack("!BIBH32sBH%dsBHIBHIBHB" % (payloadLength,),
+
+                command,                         # Command
+                frameLength,                     # Frame length
+
+                1,                               # Item 1 (Device token)
                 32,                              # Token Length
                 binaryToken,                     # Token
-                payloadLength,                   # Payload Length
-                payload,                         # Payload in JSON format
+
+                2,                               # Item 2 (Payload)
+                payloadLength,                   # Payload length
+                payload,                         # Payload
+
+                3,                               # Item 3 (Notification ID)
+                4,                               # Notification ID Length
+                identifier,                      # Notification ID
+
+                4,                               # Item 4 (Expiration)
+                4,                               # Expiration length
+                int(time.time()) + 72 * 60 * 60, # Expires in 72 hours
+
+                5,                               # Item 5 (Priority)
+                1,                               # Priority length
+                apnsPriority,                    # Priority
+
             )
         )
 
 
-
 class APNProviderFactory(ReconnectingClientFactory):
     log = Logger()
 
@@ -509,12 +580,13 @@
             # sent will be put back into the queue.
             queued = list(self.queue)
             self.queue = []
-            for (token, key), dataChangedTimestamp in queued:
-                if token and key and dataChangedTimestamp:
-                    self.sendNotification(token, key, dataChangedTimestamp)
+            for (token, key), dataChangedTimestamp, priority in queued:
+                if token and key and dataChangedTimestamp and priority:
+                    self.sendNotification(token, key, dataChangedTimestamp,
+                        priority)
 
 
-    def scheduleNotifications(self, tokens, key, dataChangedTimestamp):
+    def scheduleNotifications(self, tokens, key, dataChangedTimestamp, priority):
         """
         The starting point for getting notifications to the APNS server.  If there is
         a connection to the APNS server, these notifications are scheduled (or directly
@@ -533,15 +605,15 @@
         connection = getattr(self.factory, "connection", None)
         if connection is not None:
             if self.scheduler is not None:
-                self.scheduler.schedule(tokens, key, dataChangedTimestamp)
+                self.scheduler.schedule(tokens, key, dataChangedTimestamp, priority)
             else:
                 for token in tokens:
-                    self.sendNotification(token, key, dataChangedTimestamp)
+                    self.sendNotification(token, key, dataChangedTimestamp, priority)
         else:
-            self._saveForWhenConnected(tokens, key, dataChangedTimestamp)
+            self._saveForWhenConnected(tokens, key, dataChangedTimestamp, priority)
 
 
-    def _saveForWhenConnected(self, tokens, key, dataChangedTimestamp):
+    def _saveForWhenConnected(self, tokens, key, dataChangedTimestamp, priority):
         """
         Called in order to save notifications that can't be sent now because there
         is no connection to the APNS server.  (token, key) tuples are appended to
@@ -557,16 +629,16 @@
         """
         for token in tokens:
             tokenKeyPair = (token, key)
-            for existingPair, ignored in self.queue:
+            for existingPair, timstamp, priority in self.queue:
                 if tokenKeyPair == existingPair:
                     self.log.debug("APNProviderService has no connection; skipping duplicate: %s %s" % (token, key))
                     break # Already scheduled
             else:
                 self.log.debug("APNProviderService has no connection; queuing: %s %s" % (token, key))
-                self.queue.append(((token, key), dataChangedTimestamp))
+                self.queue.append(((token, key), dataChangedTimestamp, priority))
 
 
-    def sendNotification(self, token, key, dataChangedTimestamp):
+    def sendNotification(self, token, key, dataChangedTimestamp, priority):
         """
         If there is a connection the notification is sent right away, otherwise
         the notification is saved for later.
@@ -579,15 +651,15 @@
             which triggered this notification
         @type key: C{int}
         """
-        if not (token and key and dataChangedTimestamp):
+        if not (token and key and dataChangedTimestamp, priority):
             return
 
         # Service has reference to factory has reference to protocol instance
         connection = getattr(self.factory, "connection", None)
         if connection is None:
-            self._saveForWhenConnected([token], key, dataChangedTimestamp)
+            self._saveForWhenConnected([token], key, dataChangedTimestamp, priority)
         else:
-            connection.sendNotification(token, key, dataChangedTimestamp)
+            connection.sendNotification(token, key, dataChangedTimestamp, priority)
 
 
 

Modified: CalendarServer/trunk/calendarserver/push/notifier.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/notifier.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/notifier.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -19,7 +19,7 @@
 """
 
 from twext.enterprise.dal.record import fromTable
-from twext.enterprise.dal.syntax import Delete
+from twext.enterprise.dal.syntax import Delete, Select, Parameter
 from twext.enterprise.queue import WorkItem
 from twext.python.log import Logger
 
@@ -32,10 +32,13 @@
 
 import datetime
 
+from calendarserver.push.util import PushPriority
 
 log = Logger()
 
 
+
+
 class PushNotificationWork(WorkItem, fromTable(schema.PUSH_NOTIFICATION_WORK)):
 
     group = property(lambda self: self.pushID)
@@ -43,14 +46,36 @@
     @inlineCallbacks
     def doWork(self):
 
-        # Delete all other work items with the same pushID
-        yield Delete(From=self.table,
-                     Where=self.table.PUSH_ID == self.pushID
-                    ).on(self.transaction)
+        # Find all work items with the same push ID and find the highest
+        # priority.  Delete matching work items.
+        results = (yield Select([self.table.WORK_ID, self.table.PRIORITY,
+            self.table.PUSH_ID],
+            From=self.table, Where=self.table.PUSH_ID == self.pushID).on(
+            self.transaction))
 
+        maxPriority = self.priority
+
+        # If there are other enqueued work items for this push ID, find the
+        # highest priority one and use that value
+        if results:
+            workIDs = []
+            for workID, priority, pushID in results:
+                if priority > maxPriority:
+                    maxPriority = priority
+                workIDs.append(workID)
+
+            # Delete the work items we selected
+            yield Delete(From=self.table,
+                         Where=self.table.WORK_ID.In(
+                            Parameter("workIDs", len(workIDs)))
+                        ).on(self.transaction, workIDs=workIDs)
+
         pushDistributor = self.transaction._pushDistributor
         if pushDistributor is not None:
-            yield pushDistributor.enqueue(self.transaction, self.pushID)
+            # Convert the integer priority value back into a constant
+            priority = PushPriority.lookupByValue(maxPriority)
+            yield pushDistributor.enqueue(self.transaction, self.pushID,
+                priority=priority)
 
 
 
@@ -84,13 +109,15 @@
 
 
     @inlineCallbacks
-    def notify(self, txn):
+    def notify(self, txn, priority=PushPriority.high):
         """
         Send the notification. For a home object we just push using the home id. For a home
         child we push both the owner home id and the owned home child id.
 
         @param txn: The transaction to create the work item with
         @type txn: L{CommonStoreTransaction}
+        @param priority: the priority level
+        @type priority: L{PushPriority}
         """
         # Push ids from the store objects are a tuple of (prefix, name,) and we need to compose that
         # into a single token.
@@ -102,10 +129,13 @@
 
         for prefix, id in ids:
             if self._notify:
-                self.log.debug("Notifications are enabled: %s %s/%s" % (self._storeObject, prefix, id,))
-                yield self._notifierFactory.send(prefix, id, txn)
+                self.log.debug("Notifications are enabled: %s %s/%s priority=%d" %
+                    (self._storeObject, prefix, id, priority.value))
+                yield self._notifierFactory.send(prefix, id, txn,
+                    priority=priority)
             else:
-                self.log.debug("Skipping notification for: %s %s/%s" % (self._storeObject, prefix, id,))
+                self.log.debug("Skipping notification for: %s %s/%s" %
+                    (self._storeObject, prefix, id,))
 
 
     def clone(self, storeObject):
@@ -150,12 +180,14 @@
 
 
     @inlineCallbacks
-    def send(self, prefix, id, txn):
+    def send(self, prefix, id, txn, priority=PushPriority.high):
         """
         Enqueue a push notification work item on the provided transaction.
         """
         notBefore = datetime.datetime.utcnow() + datetime.timedelta(seconds=self.coalesceSeconds)
-        yield txn.enqueue(PushNotificationWork, pushID=self.pushKeyForId(prefix, id), notBefore=notBefore)
+        yield txn.enqueue(PushNotificationWork,
+            pushID=self.pushKeyForId(prefix, id), notBefore=notBefore,
+            priority=priority.value)
 
 
     def newNotifier(self, storeObject):
@@ -212,7 +244,7 @@
 
 
     @inlineCallbacks
-    def enqueue(self, transaction, pushKey):
+    def enqueue(self, transaction, pushKey, priority=PushPriority.high):
         """
         Pass along enqueued pushKey to any observers
 
@@ -221,6 +253,10 @@
 
         @param pushKey: the push key to distribute to the observers
         @type pushKey: C{str}
+
+        @param priority: the priority level
+        @type priority: L{PushPriority}
         """
         for observer in self.observers:
-            yield observer.enqueue(transaction, pushKey)
+            yield observer.enqueue(transaction, pushKey,
+                dataChangedTimestamp=None, priority=priority)

Modified: CalendarServer/trunk/calendarserver/push/test/test_amppush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/test_amppush.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/test/test_amppush.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -18,6 +18,7 @@
 from calendarserver.push.amppush import NotificationForID
 from twistedcaldav.test.util import StoreTestCase
 from twisted.internet.task import Clock
+from calendarserver.push.util import PushPriority
 
 class AMPPushMasterTests(StoreTestCase):
 
@@ -57,27 +58,81 @@
         self.assertTrue(client3.subscribedToID("/CalDAV/localhost/user03/"))
 
         dataChangedTimestamp = 1354815999
-        service.enqueue(None, "/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
+        service.enqueue(None, "/CalDAV/localhost/user01/",
+            dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.high)
         self.assertEquals(len(client1.history), 0)
         self.assertEquals(len(client2.history), 0)
         self.assertEquals(len(client3.history), 0)
         clock.advance(1)
-        self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp': 1354815999})])
+        self.assertEquals(
+            client1.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.high.value,
+                    }
+                )
+            ]
+        )
         self.assertEquals(len(client2.history), 0)
         self.assertEquals(len(client3.history), 0)
         clock.advance(3)
-        self.assertEquals(client2.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp': 1354815999})])
+        self.assertEquals(
+            client2.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.high.value,
+                    }
+                )
+            ]
+        )
+
         self.assertEquals(len(client3.history), 0)
         clock.advance(3)
-        self.assertEquals(client3.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp': 1354815999})])
+        self.assertEquals(
+            client3.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.high.value,
+                    }
+                )
+            ]
+        )
 
         client1.reset()
         client2.reset()
         client2.unsubscribe("token2", "/CalDAV/localhost/user01/")
-        service.enqueue(None, "/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
+        service.enqueue(None, "/CalDAV/localhost/user01/",
+            dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.low)
         self.assertEquals(len(client1.history), 0)
         clock.advance(1)
-        self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
+        self.assertEquals(
+            client1.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.low.value,
+                    }
+                )
+            ]
+        )
+
         self.assertEquals(len(client2.history), 0)
         clock.advance(3)
         self.assertEquals(len(client2.history), 0)
@@ -87,9 +142,35 @@
         client1.reset()
         client2.reset()
         client2.subscribe("token2", "/CalDAV/localhost/user01/")
-        service.enqueue(None, "/CalDAV/localhost/user01/", dataChangedTimestamp=dataChangedTimestamp)
-        self.assertEquals(client1.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
-        self.assertEquals(client2.history, [(NotificationForID, {'id': '/CalDAV/localhost/user01/', 'dataChangedTimestamp' : 1354815999})])
+        service.enqueue(None, "/CalDAV/localhost/user01/",
+            dataChangedTimestamp=dataChangedTimestamp,
+            priority=PushPriority.medium)
+        self.assertEquals(
+            client1.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.medium.value,
+                    }
+                )
+            ]
+        )
+        self.assertEquals(
+            client2.history,
+            [
+                (
+                    NotificationForID,
+                    {
+                        'id'                   : '/CalDAV/localhost/user01/',
+                        'dataChangedTimestamp' : 1354815999,
+                        'priority'             : PushPriority.medium.value,
+                    }
+                )
+            ]
+        )
 
 
 

Modified: CalendarServer/trunk/calendarserver/push/test/test_applepush.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/test_applepush.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/test/test_applepush.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -18,14 +18,15 @@
 import struct
 import time
 from calendarserver.push.applepush import (
-    ApplePushNotifierService, APNProviderProtocol
+    ApplePushNotifierService, APNProviderProtocol, ApplePushPriority
 )
-from calendarserver.push.util import validToken, TokenHistory
+from calendarserver.push.util import validToken, TokenHistory, PushPriority
 from twistedcaldav.test.util import StoreTestCase
 from twisted.internet.defer import inlineCallbacks, succeed
 from twisted.internet.task import Clock
 from txdav.common.icommondatastore import InvalidSubscriptionValues
 
+
 class ApplePushNotifierServiceTests(StoreTestCase):
 
     @inlineCallbacks
@@ -120,12 +121,14 @@
         dataChangedTimestamp = 1354815999
         txn = self._sqlCalendarStore.newTransaction()
         yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/",
-            dataChangedTimestamp=dataChangedTimestamp)
+            dataChangedTimestamp=dataChangedTimestamp, priority=PushPriority.high)
         yield txn.commit()
 
         # The notifications should be in the queue
-        self.assertTrue(((token, key1), dataChangedTimestamp) in service.providers["CalDAV"].queue)
-        self.assertTrue(((token2, key1), dataChangedTimestamp) in service.providers["CalDAV"].queue)
+        self.assertTrue(((token, key1), dataChangedTimestamp, PushPriority.high)
+            in service.providers["CalDAV"].queue)
+        self.assertTrue(((token2, key1), dataChangedTimestamp, PushPriority.high)
+            in service.providers["CalDAV"].queue)
 
         # Start the service, making the connection which should service the
         # queue
@@ -137,17 +140,40 @@
         # Verify data sent to APN
         providerConnector = service.providers["CalDAV"].testConnector
         rawData = providerConnector.transport.data
-        self.assertEquals(len(rawData), 183)
-        data = struct.unpack("!BIIH32sH", rawData[:45])
-        self.assertEquals(data[0], 1) # command
-        self.assertEquals(data[4].encode("hex"), token.replace(" ", "")) # token
-        payloadLength = data[5]
-        payload = struct.unpack("%ds" % (payloadLength,),
-            rawData[45:])
+        self.assertEquals(len(rawData), 199)
+        data = struct.unpack("!BI", rawData[:5])
+        self.assertEquals(data[0], 2) # command
+        self.assertEquals(data[1], 194) # frame length
+        # Item 1 (device token)
+        data = struct.unpack("!BH32s", rawData[5:40])
+        self.assertEquals(data[0], 1)
+        self.assertEquals(data[1], 32)
+        self.assertEquals(data[2].encode("hex"), token.replace(" ", "")) # token
+        # Item 2 (payload)
+        data = struct.unpack("!BH", rawData[40:43])
+        self.assertEquals(data[0], 2)
+        payloadLength = data[1]
+        self.assertEquals(payloadLength, 138)
+        payload = struct.unpack("!%ds" % (payloadLength,), rawData[43:181])
         payload = json.loads(payload[0])
         self.assertEquals(payload["key"], u"/CalDAV/calendars.example.com/user01/calendar/")
         self.assertEquals(payload["dataChangedTimestamp"], dataChangedTimestamp)
         self.assertTrue("pushRequestSubmittedTimestamp" in payload)
+        # Item 3 (notification id)
+        data = struct.unpack("!BHI", rawData[181:188])
+        self.assertEquals(data[0], 3)
+        self.assertEquals(data[1], 4)
+        self.assertEquals(data[2], 2)
+        # Item 4 (expiration)
+        data = struct.unpack("!BHI", rawData[188:195])
+        self.assertEquals(data[0], 4)
+        self.assertEquals(data[1], 4)
+        # Item 5 (priority)
+        data = struct.unpack("!BHB", rawData[195:199])
+        self.assertEquals(data[0], 5)
+        self.assertEquals(data[1], 1)
+        self.assertEquals(data[2], ApplePushPriority.high.value)
+
         # Verify token history is updated
         self.assertTrue(token in [t for (_ignore_i, t) in providerConnector.service.protocol.history.history])
         self.assertTrue(token2 in [t for (_ignore_i, t) in providerConnector.service.protocol.history.history])
@@ -160,14 +186,21 @@
         providerConnector.transport.data = None
         # Send notification while service is connected
         txn = self._sqlCalendarStore.newTransaction()
-        yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/")
+        yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/",
+            priority=PushPriority.low)
         yield txn.commit()
         clock.advance(1) # so that first push is sent
-        self.assertEquals(len(providerConnector.transport.data), 183)
+        self.assertEquals(len(providerConnector.transport.data), 199)
+        # Ensure that the priority is "low"
+        data = struct.unpack("!BHB", providerConnector.transport.data[195:199])
+        self.assertEquals(data[0], 5)
+        self.assertEquals(data[1], 1)
+        self.assertEquals(data[2], ApplePushPriority.low.value)
+
         # Reset sent data
         providerConnector.transport.data = None
         clock.advance(3) # so that second push is sent
-        self.assertEquals(len(providerConnector.transport.data), 183)
+        self.assertEquals(len(providerConnector.transport.data), 199)
 
         history = []
 

Modified: CalendarServer/trunk/calendarserver/push/test/test_notifier.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/test/test_notifier.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/test/test_notifier.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -22,6 +22,8 @@
 from twistedcaldav.config import ConfigDict
 from txdav.common.datastore.test.util import populateCalendarsFrom
 from txdav.common.datastore.sql_tables import _BIND_MODE_WRITE
+from calendarserver.push.util import PushPriority
+from txdav.idav import ChangeCategory
 
 
 class StubService(object):
@@ -33,8 +35,9 @@
         self.history = []
 
 
-    def enqueue(self, transaction, id):
-        self.history.append(id)
+    def enqueue(self, transaction, id, dataChangedTimestamp=None,
+        priority=None):
+        self.history.append((id, priority))
         return(succeed(None))
 
 
@@ -45,8 +48,8 @@
     def test_enqueue(self):
         stub = StubService()
         dist = PushDistributor([stub])
-        yield dist.enqueue(None, "testing")
-        self.assertEquals(stub.history, ["testing"])
+        yield dist.enqueue(None, "testing", PushPriority.high)
+        self.assertEquals(stub.history, [("testing", PushPriority.high)])
 
 
     def test_getPubSubAPSConfiguration(self):
@@ -91,8 +94,9 @@
         self.history = []
 
 
-    def enqueue(self, transaction, pushID):
-        self.history.append(pushID)
+    def enqueue(self, transaction, pushID, dataChangedTimestamp=None,
+        priority=None):
+        self.history.append((pushID, priority))
 
 
 
@@ -111,35 +115,62 @@
         txn = self._sqlCalendarStore.newTransaction()
         wp = (yield txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/foo/",
+            priority=PushPriority.high.value
         ))
         yield txn.commit()
         yield wp.whenExecuted()
-        self.assertEquals(pushDistributor.history, ["/CalDAV/localhost/foo/"])
+        self.assertEquals(pushDistributor.history,
+            [("/CalDAV/localhost/foo/", PushPriority.high)])
 
         pushDistributor.reset()
         txn = self._sqlCalendarStore.newTransaction()
         wp = (yield txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
+            priority=PushPriority.high.value
         ))
         wp = (yield txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
+            priority=PushPriority.high.value
         ))
         wp = (yield txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
+            priority=PushPriority.high.value
         ))
         # Enqueue a different pushID to ensure those are not grouped with
         # the others:
         wp = (yield txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/baz/",
+            priority=PushPriority.high.value
         ))
 
         yield txn.commit()
         yield wp.whenExecuted()
+        self.assertEquals(set(pushDistributor.history),
+            set([("/CalDAV/localhost/bar/", PushPriority.high),
+             ("/CalDAV/localhost/baz/", PushPriority.high)]))
+
+        # Ensure only the high-water-mark priority push goes out, by
+        # enqueuing low, medium, and high notifications
+        pushDistributor.reset()
+        txn = self._sqlCalendarStore.newTransaction()
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID="/CalDAV/localhost/bar/",
+            priority=PushPriority.low.value
+        ))
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID="/CalDAV/localhost/bar/",
+            priority=PushPriority.high.value
+        ))
+        wp = (yield txn.enqueue(PushNotificationWork,
+            pushID="/CalDAV/localhost/bar/",
+            priority=PushPriority.medium.value
+        ))
+        yield txn.commit()
+        yield wp.whenExecuted()
         self.assertEquals(pushDistributor.history,
-            ["/CalDAV/localhost/bar/", "/CalDAV/localhost/baz/"])
+            [("/CalDAV/localhost/bar/", PushPriority.high)])
 
 
-
 class NotifierFactory(StoreTestCase):
 
     requirements = {
@@ -168,8 +199,9 @@
     def test_homeNotifier(self):
 
         home = yield self.homeUnderTest()
-        yield home.notifyChanged()
-        self.assertEquals(self.notifierFactory.history, ["/CalDAV/example.com/home1/"])
+        yield home.notifyChanged(category=ChangeCategory.default)
+        self.assertEquals(self.notifierFactory.history,
+            [("/CalDAV/example.com/home1/", PushPriority.high)])
         yield self.commit()
 
 
@@ -177,10 +209,12 @@
     def test_calendarNotifier(self):
 
         calendar = yield self.calendarUnderTest()
-        yield calendar.notifyChanged()
+        yield calendar.notifyChanged(category=ChangeCategory.default)
         self.assertEquals(
             set(self.notifierFactory.history),
-            set(["/CalDAV/example.com/home1/", "/CalDAV/example.com/home1/calendar_1/"])
+            set([
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high)])
         )
         yield self.commit()
 
@@ -194,9 +228,9 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/calendar_1/",
-                "/CalDAV/example.com/home2/"
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high),
+                ("/CalDAV/example.com/home2/", PushPriority.high),
             ])
         )
         yield self.commit()
@@ -207,9 +241,9 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/calendar_1/",
-                "/CalDAV/example.com/home2/"
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high),
+                ("/CalDAV/example.com/home2/", PushPriority.high),
             ])
         )
         yield self.commit()
@@ -225,10 +259,12 @@
         self.notifierFactory.reset()
 
         shared = yield self.calendarUnderTest(home="home2", name=shareName)
-        yield shared.notifyChanged()
+        yield shared.notifyChanged(category=ChangeCategory.default)
         self.assertEquals(
             set(self.notifierFactory.history),
-            set(["/CalDAV/example.com/home1/", "/CalDAV/example.com/home1/calendar_1/"])
+            set([
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high)])
         )
         yield self.commit()
 
@@ -237,9 +273,11 @@
     def test_notificationNotifier(self):
 
         notifications = yield self.transactionUnderTest().notificationsWithUID("home1")
-        yield notifications.notifyChanged()
+        yield notifications.notifyChanged(category=ChangeCategory.default)
         self.assertEquals(
             set(self.notifierFactory.history),
-            set(["/CalDAV/example.com/home1/", "/CalDAV/example.com/home1/notification/"])
+            set([
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/notification/", PushPriority.high)])
         )
         yield self.commit()

Modified: CalendarServer/trunk/calendarserver/push/util.py
===================================================================
--- CalendarServer/trunk/calendarserver/push/util.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/push/util.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -16,7 +16,20 @@
 
 from OpenSSL import crypto
 from twext.python.log import Logger
+from twisted.python.constants import Values, ValueConstant
 
+
+
+class PushPriority(Values):
+    """
+    Constants to use for push priorities
+    """
+    low    = ValueConstant(1)
+    medium = ValueConstant(5)
+    high   = ValueConstant(10)
+
+
+
 def getAPNTopicFromCertificate(certPath):
     """
     Given the path to a certificate, extract the UID value portion of the
@@ -128,7 +141,7 @@
         self.staggerSeconds = staggerSeconds
 
 
-    def schedule(self, tokens, key, dataChangedTimestamp):
+    def schedule(self, tokens, key, dataChangedTimestamp, priority):
         """
         Schedules a batch of notifications for the given tokens, staggered
         with self.staggerSeconds between each one.  Duplicates are ignored,
@@ -151,13 +164,14 @@
                     (internalKey,))
             else:
                 self.outstanding[internalKey] = self.reactor.callLater(
-                    scheduleTime, self.send, token, key, dataChangedTimestamp)
+                    scheduleTime, self.send, token, key, dataChangedTimestamp,
+                    priority)
                 self.log.debug("PushScheduler scheduled: %s in %.0f sec" %
                     (internalKey, scheduleTime))
                 scheduleTime += self.staggerSeconds
 
 
-    def send(self, token, key, dataChangedTimestamp):
+    def send(self, token, key, dataChangedTimestamp, priority):
         """
         This method is what actually gets scheduled.  Its job is to remove
         its corresponding entry from the outstanding dict and call the
@@ -173,7 +187,7 @@
         """
         self.log.debug("PushScheduler fired for %s %s %d" % (token, key, dataChangedTimestamp))
         del self.outstanding[(token, key)]
-        return self.callback(token, key, dataChangedTimestamp)
+        return self.callback(token, key, dataChangedTimestamp, priority)
 
 
     def stop(self):

Modified: CalendarServer/trunk/calendarserver/tools/ampnotifications.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/ampnotifications.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/tools/ampnotifications.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -141,8 +141,8 @@
 
 
 
-def notificationCallback(id, dataChangedTimestamp):
-    print("Received notification for:", id)
+def notificationCallback(id, dataChangedTimestamp, priority):
+    print("Received notification for:", id, "Priority", priority)
     return succeed(True)
 
 

Modified: CalendarServer/trunk/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/gateway.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/tools/gateway.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -156,6 +156,7 @@
     'ZIP' : { 'extras' : True, 'attr' : 'zip', },
     'Country' : { 'extras' : True, 'attr' : 'country', },
     'Phone' : { 'extras' : True, 'attr' : 'phone', },
+    'Geo' : { 'extras' : True, 'attr' : 'geo', },
     'AutoSchedule' : { 'attr' : 'autoSchedule', },
     'AutoAcceptGroup' : { 'attr' : 'autoAcceptGroup', },
 }

Modified: CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -193,6 +193,7 @@
         self.assertEquals(record.extras["zip"], "95014")
         self.assertEquals(record.extras["country"], "USA")
         self.assertEquals(record.extras["phone"], "(408) 555-1212")
+        self.assertEquals(record.extras["geo"], "geo:37.331,-122.030")
 
         results = yield self.runCommand(command_getLocationAttributes)
         self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03', 'user04']))
@@ -424,6 +425,8 @@
         <string>USA</string>
         <key>Phone</key>
         <string>(408) 555-1212</string>
+        <key>Geo</key>
+        <string>geo:37.331,-122.030</string>
         <key>ReadProxies</key>
         <array>
             <string>users:user03</string>

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -89,6 +89,8 @@
     InvalidUIDError, UIDExistsError, UIDExistsElsewhereError, \
     InvalidResourceMove, InvalidComponentForStoreError
 
+from txdav.idav import ChangeCategory
+
 from pycalendar.datetime import DateTime
 from pycalendar.duration import Duration
 from pycalendar.timezone import Timezone
@@ -2192,8 +2194,18 @@
         else:
             yield self._calendar._updateRevision(self._name)
 
-        yield self._calendar.notifyChanged()
+        # Determine change category
+        category = ChangeCategory.default
+        if internal_state == ComponentUpdateState.INBOX:
+            category = ChangeCategory.inbox
+        elif internal_state == ComponentUpdateState.ORGANIZER_ITIP_UPDATE:
+            category = ChangeCategory.organizerITIPUpdate
+        elif (internal_state == ComponentUpdateState.ATTENDEE_ITIP_UPDATE and
+            hasattr(self._txn, "doing_attende_refresh")):
+            category = ChangeCategory.attendeeITIPUpdate
 
+        yield self._calendar.notifyChanged(category=category)
+
         # Finally check if a split is needed
         if internal_state not in (ComponentUpdateState.SPLIT_OWNER, ComponentUpdateState.SPLIT_ATTENDEE,) and schedule_state == "organizer":
             yield self.checkSplit()

Modified: CalendarServer/trunk/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/caldav/datastore/test/common.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -53,7 +53,9 @@
 from txdav.common.icommondatastore import ConcurrentModification
 from twistedcaldav.ical import Component
 from twistedcaldav.config import config
+from calendarserver.push.util import PushPriority
 
+
 storePath = FilePath(__file__).parent().child("calendar_store")
 
 homeRoot = storePath.child("ho").child("me").child("home1")
@@ -456,8 +458,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/notification/",
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/notification/", PushPriority.high),
             ])
         )
         yield self.commit()
@@ -474,8 +476,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/notification/",
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/notification/", PushPriority.high),
             ])
         )
         yield self.commit()
@@ -698,7 +700,7 @@
         calendarProperties = (yield home.calendarWithName(name)).properties()
         self.assertEqual(len(calendarProperties), 0)
         # notify is called prior to commit
-        self.assertTrue("/CalDAV/example.com/home1/" in self.notifierFactory.history)
+        self.assertTrue(("/CalDAV/example.com/home1/", PushPriority.high) in self.notifierFactory.history)
         yield self.commit()
 
         # Make sure it's available in a new transaction; i.e. test the commit.
@@ -741,10 +743,10 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/calendar_1/",
-                "/CalDAV/example.com/home1/calendar_2/",
-                "/CalDAV/example.com/home1/calendar_empty/",
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_2/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_empty/", PushPriority.high),
             ])
         )
 
@@ -918,8 +920,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/calendar_1/",
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high),
             ])
         )
         yield self.commit()
@@ -1474,8 +1476,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/calendar_1/",
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high),
             ])
         )
         yield self.commit()
@@ -1593,8 +1595,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CalDAV/example.com/home1/",
-                "/CalDAV/example.com/home1/calendar_1/",
+                ("/CalDAV/example.com/home1/", PushPriority.high),
+                ("/CalDAV/example.com/home1/calendar_1/", PushPriority.high),
             ])
         )
         yield self.commit()

Modified: CalendarServer/trunk/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/carddav/datastore/test/common.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -39,7 +39,9 @@
 from txdav.common.icommondatastore import ObjectResourceNameAlreadyExistsError
 from txdav.idav import IPropertyStore, IDataStore
 from txdav.xml.element import WebDAVUnknownElement
+from calendarserver.push.util import PushPriority
 
+
 storePath = FilePath(__file__).parent().child("addressbook_store")
 
 home1Root = storePath.child("ho").child("me").child("home1")
@@ -372,7 +374,7 @@
         yield home.removeAddressBookWithName(name)
         self.assertNotIdentical((yield home.addressbookWithName(name)), None)
         # notify is called prior to commit
-        self.assertTrue("/CardDAV/example.com/home1/" in self.notifierFactory.history)
+        self.assertTrue(("/CardDAV/example.com/home1/", PushPriority.high) in self.notifierFactory.history)
         yield self.commit()
 
         # Make sure it's available in a new transaction; i.e. test the commit.
@@ -399,8 +401,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CardDAV/example.com/home1/",
-                "/CardDAV/example.com/home1/addressbook/",
+                ("/CardDAV/example.com/home1/", PushPriority.high),
+                ("/CardDAV/example.com/home1/addressbook/", PushPriority.high),
             ])
         )
 
@@ -532,8 +534,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CardDAV/example.com/home1/",
-                "/CardDAV/example.com/home1/addressbook/",
+                ("/CardDAV/example.com/home1/", PushPriority.high),
+                ("/CardDAV/example.com/home1/addressbook/", PushPriority.high),
             ])
         )
 
@@ -693,8 +695,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CardDAV/example.com/home1/",
-                "/CardDAV/example.com/home1/addressbook/",
+                ("/CardDAV/example.com/home1/", PushPriority.high),
+                ("/CardDAV/example.com/home1/addressbook/", PushPriority.high),
             ])
         )
 
@@ -809,8 +811,8 @@
         self.assertEquals(
             set(self.notifierFactory.history),
             set([
-                "/CardDAV/example.com/home1/",
-                "/CardDAV/example.com/home1/addressbook/",
+                ("/CardDAV/example.com/home1/", PushPriority.high),
+                ("/CardDAV/example.com/home1/addressbook/", PushPriority.high),
             ])
         )
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/common/datastore/sql.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -73,6 +73,7 @@
 from txdav.common.inotifications import INotificationCollection, \
     INotificationObject
 from txdav.xml.parser import WebDAVDocument
+from txdav.idav import ChangeCategory
 
 from uuid import uuid4, UUID
 
@@ -2236,7 +2237,7 @@
 
 
     @inlineCallbacks
-    def notifyChanged(self):
+    def notifyChanged(self, category=ChangeCategory.default):
         """
         Send notifications, change sync token and bump last modified because
         the resource has changed.  We ensure we only do this once per object
@@ -2260,7 +2261,7 @@
             # push notifiers add their work items immediately
             notifier = self._notifiers.get("push", None)
             if notifier:
-                yield notifier.notify(self._txn)
+                yield notifier.notify(self._txn, priority=category.value)
 
 
     @classproperty
@@ -4297,11 +4298,11 @@
         return self.ownerHome().notifierID()
 
 
-    def notifyChanged(self):
+    def notifyChanged(self, category=ChangeCategory.default):
         """
         Send notifications when a child resource is changed.
         """
-        return self._notifyChanged(property_change=False)
+        return self._notifyChanged(property_change=False, category=category)
 
 
     def notifyPropertyChanged(self):
@@ -4312,7 +4313,8 @@
 
 
     @inlineCallbacks
-    def _notifyChanged(self, property_change=False):
+    def _notifyChanged(self, property_change=False,
+            category=ChangeCategory.default):
         """
         Send notifications, change sync token and bump last modified because
         the resource has changed.  We ensure we only do this once per object
@@ -4348,7 +4350,7 @@
             # push notifiers add their work items immediately
             notifier = self._notifiers.get("push", None)
             if notifier:
-                yield notifier.notify(self._txn)
+                yield notifier.notify(self._txn, priority=category.value)
 
 
     @classproperty
@@ -5162,7 +5164,7 @@
 
 
     @inlineCallbacks
-    def notifyChanged(self):
+    def notifyChanged(self, category=ChangeCategory.default):
         """
         Send notifications, change sync token and bump last modified because
         the resource has changed.  We ensure we only do this once per object
@@ -5181,7 +5183,7 @@
             # push notifiers add their work items immediately
             notifier = self._notifiers.get("push", None)
             if notifier:
-                yield notifier.notify(self._txn)
+                yield notifier.notify(self._txn, priority=category.value)
 
         returnValue(None)
 

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current-oracle-dialect.sql	2013-11-12 21:38:00 UTC (rev 11934)
@@ -347,7 +347,8 @@
 create table PUSH_NOTIFICATION_WORK (
     "WORK_ID" integer primary key not null,
     "NOT_BEFORE" timestamp default CURRENT_TIMESTAMP at time zone 'UTC',
-    "PUSH_ID" nvarchar2(255)
+    "PUSH_ID" nvarchar2(255),
+    "PRIORITY" integer not null
 );
 
 create table GROUP_CACHER_POLLING_WORK (
@@ -366,7 +367,7 @@
     "VALUE" nvarchar2(255)
 );
 
-insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '27');
+insert into CALENDARSERVER (NAME, VALUE) values ('VERSION', '28');
 insert into CALENDARSERVER (NAME, VALUE) values ('CALENDAR-DATAVERSION', '5');
 insert into CALENDARSERVER (NAME, VALUE) values ('ADDRESSBOOK-DATAVERSION', '2');
 create index CALENDAR_HOME_METADAT_3cb9049e on CALENDAR_HOME_METADATA (

Modified: CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/common/datastore/sql_schema/current.sql	2013-11-12 21:38:00 UTC (rev 11934)
@@ -663,7 +663,8 @@
 create table PUSH_NOTIFICATION_WORK (
   WORK_ID                       integer      primary key default nextval('WORKITEM_SEQ') not null, -- implicit index
   NOT_BEFORE                    timestamp    default timezone('UTC', CURRENT_TIMESTAMP),
-  PUSH_ID                       varchar(255) not null
+  PUSH_ID                       varchar(255) not null,
+  PRIORITY                      integer      not null -- 1:low 5:medium 10:high
 );
 
 -----------------
@@ -698,6 +699,6 @@
   VALUE                         varchar(255)
 );
 
-insert into CALENDARSERVER values ('VERSION', '27');
+insert into CALENDARSERVER values ('VERSION', '28');
 insert into CALENDARSERVER values ('CALENDAR-DATAVERSION', '5');
 insert into CALENDARSERVER values ('ADDRESSBOOK-DATAVERSION', '2');

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -726,8 +726,8 @@
         return "/%s/%s/%s/" % (prefix, self.hostname, id)
 
 
-    def send(self, prefix, id, txn):
-        self.history.append(self.pushKeyForId(prefix, id))
+    def send(self, prefix, id, txn, priority): 
+        self.history.append((self.pushKeyForId(prefix, id), priority))
 
 
     def reset(self):

Modified: CalendarServer/trunk/txdav/idav.py
===================================================================
--- CalendarServer/trunk/txdav/idav.py	2013-11-12 00:22:37 UTC (rev 11933)
+++ CalendarServer/trunk/txdav/idav.py	2013-11-12 21:38:00 UTC (rev 11934)
@@ -34,6 +34,9 @@
 from zope.interface import Attribute, Interface
 from zope.interface.common.mapping import IMapping
 
+from twisted.python.constants import Values, ValueConstant
+from calendarserver.push.util import PushPriority
+
 #
 # Exceptions
 #
@@ -231,6 +234,19 @@
 
 
 
+class ChangeCategory(Values):
+    """
+    Constants to use for notifyChanged's category parameter.  Maps
+    types of changes to the appropriate push priority level.
+    TODO: make these values configurable in plist perhaps.
+    """
+    default             = ValueConstant(PushPriority.high)
+    inbox               = ValueConstant(PushPriority.medium)
+    attendeeITIPUpdate  = ValueConstant(PushPriority.medium)
+    organizerITIPUpdate = ValueConstant(PushPriority.medium)
+
+
+
 class INotifier(Interface):
     """
     Interface for an object that can send change notifications. Notifiers are associated with specific notifier factories
@@ -260,9 +276,12 @@
         @rtype: L{IStoreNotifier} or C{None}
         """
 
-    def notifyChanged(): #@NoSelf
+    def notifyChanged(category): #@NoSelf
         """
         Send a change notification to any notifiers assigned to the object.
+
+        @param category: the kind of change triggering this notification
+        @type: L{ChangeCategory}
         """
 
     def notifierID(): #@NoSelf
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/5ec58497/attachment.html>


More information about the calendarserver-changes mailing list