[CalendarServer-changes] [12016] CalendarServer/branches/users/cdaboo/sharing-in-the-store

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


Revision: 12016
          http://trac.calendarserver.org//changeset/12016
Author:   cdaboo at apple.com
Date:     2013-12-02 11:04:21 -0800 (Mon, 02 Dec 2013)
Log Message:
-----------
Merge from trunk.

Modified Paths:
--------------
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/_calendarserver_preamble.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/push/test/test_notifier.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/tools/test/test_calverify.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/caldavd-test.plist
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/resources/caldavd-resources.plist
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/contrib/performance/loadtest/ical.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/build.sh
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/version.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/test
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/internet/sendfdport.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/aggregate.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/directory.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/index.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_aggregate.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_directory.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_xml.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/xml.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/ical.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_config.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_icalendar.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Casablanca.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Tripoli.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Eirunepe.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Porto_Acre.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Rio_Branco.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Brazil/Acre.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Libya.ics
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/timezones.xml
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/version.txt
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/base.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/test/base.py

Added Paths:
-----------
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/trial
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/twistd
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/application/
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/protocols/echo.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_index.py
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/twisted/plugins/masterchild.py

Property Changed:
----------------
    CalendarServer/branches/users/cdaboo/sharing-in-the-store/


Property changes on: CalendarServer/branches/users/cdaboo/sharing-in-the-store
___________________________________________________________________
Modified: svn:mergeinfo
   - /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/json:11622-11912
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
/CalendarServer/branches/users/glyph/hang-fix:11465-11491
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:11935-11938
   + /CalDAVTester/trunk:11193-11198
/CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/release/CalendarServer-4.3-dev:10180-10190,10192
/CalendarServer/branches/release/CalendarServer-5.1-dev:11846
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/component-set-fixes:8130-8346
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/fix-no-ischedule:11607-11871
/CalendarServer/branches/users/cdaboo/implicituidrace:8137-8141
/CalendarServer/branches/users/cdaboo/ischedule-dkim:9747-9979
/CalendarServer/branches/users/cdaboo/json:11622-11912
/CalendarServer/branches/users/cdaboo/managed-attachments:9985-10145
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/performance-tweaks:11824-11836
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/queued-attendee-refreshes:7740-8287
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/reverse-proxy-pods:11875-11900
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/cdaboo/store-scheduling:10876-11129
/CalendarServer/branches/users/cdaboo/timezones:7443-7699
/CalendarServer/branches/users/cdaboo/txn-debugging:8730-8743
/CalendarServer/branches/users/gaya/sharedgroups-3:11088-11204
/CalendarServer/branches/users/glyph/always-abort-txn-on-error:9958-9969
/CalendarServer/branches/users/glyph/case-insensitive-uid:8772-8805
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/digest-auth-redux:10624-10635
/CalendarServer/branches/users/glyph/disable-quota:7718-7727
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/enforce-max-requests:11640-11643
/CalendarServer/branches/users/glyph/hang-fix:11465-11491
/CalendarServer/branches/users/glyph/imip-and-admin-html:7866-7984
/CalendarServer/branches/users/glyph/ipv6-client:9054-9105
/CalendarServer/branches/users/glyph/launchd-wrapper-bis:11413-11436
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/log-cleanups:11691-11731
/CalendarServer/branches/users/glyph/migrate-merge:8690-8713
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/multiget-delete:8321-8330
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/one-home-list-api:10048-10073
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/other-html:8062-8091
/CalendarServer/branches/users/glyph/parallel-sim:8240-8251
/CalendarServer/branches/users/glyph/parallel-upgrade:8376-8400
/CalendarServer/branches/users/glyph/parallel-upgrade_to_1:8571-8583
/CalendarServer/branches/users/glyph/q:9560-9688
/CalendarServer/branches/users/glyph/queue-locking-and-timing:10204-10289
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/shared-pool-fixes:8436-8443
/CalendarServer/branches/users/glyph/shared-pool-take2:8155-8174
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sharing-api:9192-9205
/CalendarServer/branches/users/glyph/skip-lonely-vtimezones:8524-8535
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/start-service-start-loop:11060-11065
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/table-alias:8651-8664
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/unshare-when-access-revoked:10562-10595
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/glyph/uuid-normalize:9268-9296
/CalendarServer/branches/users/glyph/warning-cleanups:11347-11357
/CalendarServer/branches/users/glyph/whenNotProposed:11881-11897
/CalendarServer/branches/users/glyph/xattrs-from-files:7757-7769
/CalendarServer/branches/users/sagen/applepush:8126-8184
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/sagen/testing:10827-10851,10853-10855
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:11935-12015

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/_calendarserver_preamble.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/_calendarserver_preamble.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/_calendarserver_preamble.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -64,6 +64,7 @@
         "calendarserver_manage_postgres",
         "calendarserver_manage_timezones",
         "icalendar_split",
+        "twistd", "trial",
     ]
 
     if split(sys.argv[0])[-1] not in noConfigOption:

Copied: CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/trial (from rev 12014, CalendarServer/trunk/bin/trial)
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/trial	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/trial	2013-12-02 19:04:21 UTC (rev 12016)
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+    if "PYTHONPATH" in globals():
+        sys.path.insert(0, PYTHONPATH)
+    else:
+        try:
+            import _calendarserver_preamble
+        except ImportError:
+            sys.exc_clear()
+
+    from twisted.scripts.trial import run
+    run()

Copied: CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/twistd (from rev 12014, CalendarServer/trunk/bin/twistd)
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/twistd	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/bin/twistd	2013-12-02 19:04:21 UTC (rev 12016)
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+    if "PYTHONPATH" in globals():
+        sys.path.insert(0, PYTHONPATH)
+    else:
+        try:
+            import _calendarserver_preamble
+        except ImportError:
+            sys.exc_clear()
+
+    from twisted.scripts.twistd import run
+    run()

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/push/test/test_notifier.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/push/test/test_notifier.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/push/test/test_notifier.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -18,14 +18,13 @@
 from calendarserver.push.notifier import PushDistributor
 from calendarserver.push.notifier import getPubSubAPSConfiguration
 from calendarserver.push.notifier import PushNotificationWork
-from twisted.internet.defer import inlineCallbacks, succeed
+from twisted.internet.defer import inlineCallbacks, succeed, gatherResults
 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):
     def __init__(self):
         self.reset()
@@ -97,6 +96,7 @@
     def enqueue(self, transaction, pushID, dataChangedTimestamp=None,
         priority=None):
         self.history.append((pushID, priority))
+        return(succeed(None))
 
 
 
@@ -124,27 +124,32 @@
 
         pushDistributor.reset()
         txn = self._sqlCalendarStore.newTransaction()
-        wp = (yield txn.enqueue(PushNotificationWork,
+        proposals = []
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
             priority=PushPriority.high.value
-        ))
-        wp = (yield txn.enqueue(PushNotificationWork,
+        )
+        proposals.append(wp.whenExecuted())
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
             priority=PushPriority.high.value
-        ))
-        wp = (yield txn.enqueue(PushNotificationWork,
+        )
+        proposals.append(wp.whenExecuted())
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
             priority=PushPriority.high.value
-        ))
+        )
+        proposals.append(wp.whenExecuted())
         # Enqueue a different pushID to ensure those are not grouped with
         # the others:
-        wp = (yield txn.enqueue(PushNotificationWork,
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/baz/",
             priority=PushPriority.high.value
-        ))
+        )
+        proposals.append(wp.whenExecuted())
 
         yield txn.commit()
-        yield wp.whenExecuted()
+        yield gatherResults(proposals)
         self.assertEquals(set(pushDistributor.history),
             set([("/CalDAV/localhost/bar/", PushPriority.high),
              ("/CalDAV/localhost/baz/", PushPriority.high)]))
@@ -153,20 +158,24 @@
         # enqueuing low, medium, and high notifications
         pushDistributor.reset()
         txn = self._sqlCalendarStore.newTransaction()
-        wp = (yield txn.enqueue(PushNotificationWork,
+        proposals = []
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
             priority=PushPriority.low.value
-        ))
-        wp = (yield txn.enqueue(PushNotificationWork,
+        )
+        proposals.append(wp.whenExecuted())
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
             priority=PushPriority.high.value
-        ))
-        wp = (yield txn.enqueue(PushNotificationWork,
+        )
+        proposals.append(wp.whenExecuted())
+        wp = txn.enqueue(PushNotificationWork,
             pushID="/CalDAV/localhost/bar/",
             priority=PushPriority.medium.value
-        ))
+        )
+        proposals.append(wp.whenExecuted())
         yield txn.commit()
-        yield wp.whenExecuted()
+        yield gatherResults(proposals)
         self.assertEquals(pushDistributor.history,
             [("/CalDAV/localhost/bar/", PushPriority.high)])
 

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/tools/test/test_calverify.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/tools/test/test_calverify.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/calendarserver/tools/test/test_calverify.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -511,7 +511,7 @@
         """
 
         sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": True,
@@ -555,7 +555,7 @@
         """
 
         sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": True,
@@ -627,7 +627,7 @@
         """
 
         sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -667,7 +667,7 @@
         """
 
         sync_token_old = (yield (yield self.calendarUnderTest()).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -1418,7 +1418,7 @@
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_old2 = (yield (yield self.calendarUnderTest(home=self.uuid2, name="calendar")).syncToken())
         sync_token_old3 = (yield (yield self.calendarUnderTest(home=self.uuid3, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -1485,7 +1485,7 @@
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_old2 = (yield (yield self.calendarUnderTest(home=self.uuid2, name="calendar")).syncToken())
         sync_token_old3 = (yield (yield self.calendarUnderTest(home=self.uuid3, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -1576,7 +1576,7 @@
         self.assertNotEqual(sync_token_old3, sync_token_new3)
 
         # Re-scan after changes to make sure there are no errors
-        self.commit()
+        yield self.commit()
         options["fix"] = False
         calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
         yield calverify.doAction()
@@ -1694,7 +1694,7 @@
 
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -1745,7 +1745,7 @@
 
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -1803,7 +1803,7 @@
         self.assertNotEqual(sync_token_oldl1, sync_token_newl1)
 
         # Re-scan after changes to make sure there are no errors
-        self.commit()
+        yield self.commit()
         options["fix"] = False
         calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
         yield calverify.doAction()
@@ -1921,7 +1921,7 @@
 
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -1970,7 +1970,7 @@
 
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -2021,7 +2021,7 @@
         self.assertNotEqual(sync_token_oldl1, sync_token_newl1)
 
         # Re-scan after changes to make sure there are no errors
-        self.commit()
+        yield self.commit()
         options["fix"] = False
         options["uuid"] = CalVerifyMismatchTestsBase.uuidl1
         calverify = SchedulingMismatchService(self._sqlCalendarStore, options, output, reactor, config)
@@ -2430,7 +2430,7 @@
 
         sync_token_old1 = (yield (yield self.calendarUnderTest(home=self.uuid1, name="calendar")).syncToken())
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -2592,7 +2592,7 @@
         """
 
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -2639,7 +2639,7 @@
         """
 
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -2678,7 +2678,7 @@
         self.assertNotEqual(sync_token_oldl1, sync_token_newl1)
 
         # Re-scan after changes to make sure there are no errors
-        self.commit()
+        yield self.commit()
         options["fix"] = False
         options["uuid"] = self.uuidl1
         calverify = DarkPurgeService(self._sqlCalendarStore, options, output, reactor, config)
@@ -2698,7 +2698,7 @@
         """
 
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -2737,7 +2737,7 @@
         self.assertNotEqual(sync_token_oldl1, sync_token_newl1)
 
         # Re-scan after changes to make sure there are no errors
-        self.commit()
+        yield self.commit()
         options["fix"] = False
         options["uuid"] = self.uuidl1
         calverify = DarkPurgeService(self._sqlCalendarStore, options, output, reactor, config)
@@ -2757,7 +2757,7 @@
         """
 
         sync_token_oldl1 = (yield (yield self.calendarUnderTest(home=self.uuidl1, name="calendar")).syncToken())
-        self.commit()
+        yield self.commit()
 
         options = {
             "ical": False,
@@ -2796,7 +2796,7 @@
         self.assertNotEqual(sync_token_oldl1, sync_token_newl1)
 
         # Re-scan after changes to make sure there are no errors
-        self.commit()
+        yield self.commit()
         options["fix"] = False
         options["uuid"] = self.uuidl1
         calverify = DarkPurgeService(self._sqlCalendarStore, options, output, reactor, config)

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/caldavd-test.plist	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/caldavd-test.plist	2013-12-02 19:04:21 UTC (rev 12016)
@@ -895,9 +895,10 @@
     <key>EnableWebAdmin</key>
     <true/>
 
-    <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
+    <!-- Support for Content-Encoding compression -->
     <key>ResponseCompression</key>
-    <false/>
+    <false/>  <!-- Off for testing, as debugging is easier that way. -->
+
     
     <!-- The retry-after value (in seconds) to return with a 503 error. -->
     <key>HTTPRetryAfter</key>

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/resources/caldavd-resources.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/resources/caldavd-resources.plist	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/conf/resources/caldavd-resources.plist	2013-12-02 19:04:21 UTC (rev 12016)
@@ -669,7 +669,7 @@
     <key>EnableWebAdmin</key>
     <true/>
 
-    <!-- Support for Content-Encoding compression options as specified in RFC2616 Section 3.5 -->
+    <!-- Support for Content-Encoding compression -->
     <key>ResponseCompression</key>
     <false/>
     

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/contrib/performance/loadtest/ical.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/contrib/performance/loadtest/ical.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -1199,7 +1199,7 @@
         connect(GAIEndpoint(self.reactor, host, port), factory)
 
 
-    def _receivedPush(self, inboundID, dataChangedTimestamp):
+    def _receivedPush(self, inboundID, dataChangedTimestamp, priority=5):
         for href, id in self.ampPushKeys.iteritems():
             if inboundID == id:
                 self._checkCalendarsForEvents(href, push=True)

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/build.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/build.sh	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/build.sh	2013-12-02 19:04:21 UTC (rev 12016)
@@ -40,11 +40,67 @@
   fi;
 }
 
+# Checks for presence of a C header, optionally with a version comparison.
+# With only a header file name, try to include it, returning nonzero if absent.
+# With 3 params, also attempt a version check, returning nonzero if too old.
+# Param 2 is a minimum acceptable version number
+# Param 3 is a #define from the source that holds the installed version number
+# Examples:
+#   Assert that ldap.h is present
+#     find_header "ldap.h"
+#   Assert that ldap.h is present with a version >= 20344
+#     find_header "ldap.h" 20344 "LDAP_VENDOR_VERSION"
 find_header () {
-  local sysheader="$1"; shift;
-  echo "#include <${sysheader}>" | cc -x c -c - -o /dev/null 2> /dev/null;
-  return "$?";
-}
+  ARGS="$@";
+  ret=1;  # default to a failed check, forcing a fetch of the depencency
+  i=0;
+  for a in $ARGS; do
+    [ $i -eq 0 ] && local sysheader="$1";
+    [ $i -eq 1 ] && local minver="$2";
+    [ $i -eq 2 ] && local def="$3";
+    i=$(($i+1));
+  done;
+  [ ! $sysheader ] && return 1;
+  # Check for presence of a header. We use the "-c" cc option because we don't
+  # need to emit a file; cc exits nonzero if it can't find the header
+  if [ $# -lt 2 ]; then
+    echo "#include <${sysheader}>" | cc -x c -c - -o /dev/null 2> /dev/null;
+    return "$?";
+  # Check for presence of a header of specified version
+  else
+    found='';
+    local aout=$(mktemp -t ccXXXXXX); # compiled executable file path
+    local prog=$(mktemp -t ccXXXXXX); # C source file path
+    cat <<DOC > ${prog}
+#include <${sysheader}>
+#include <stdio.h>
+#define STR(x)   #x
+#define SHOW_DEFINE(x) printf("%s", STR(x))
+int main()
+{
+    if (${def})
+    {
+        SHOW_DEFINE(${def});
+        return 0;
+    };
+    return 1;
+};
+DOC
+    cc -x c -o ${aout} ${prog} &> /dev/null;
+    if [ $? -eq 0 ] && [ -e ${aout} ] ; then
+      found=$(${aout});
+    fi;
+    if [ $? -eq 0 ] && [ ! -z ${found} ] ; then
+      cmp_version $minver $found;
+      ret=$?;
+    else
+      ret=1;   #cc exited nonzero or didn't emit a file
+    fi;
+    rm -f "${aout}";
+    rm -f "${prog}";
+  fi;
+  return $ret;
+};
 
 # Initialize all the global state required to use this library.
 init_build () {
@@ -213,10 +269,13 @@
   if "${force_setup}" || [ ! -d "${path}" ]; then
     local ext="$(echo "${url}" | sed 's|^.*\.\([^.]*\)$|\1|')";
 
+    untar () { tar -xvf -; }
+    unzipstream () { tmp="$(mktemp -t ccsXXXXX)"; cat > "${tmp}"; unzip "${tmp}"; rm "${tmp}"; }
     case "${ext}" in
-      gz|tgz) decompress="gzip -d -c"; ;;
-      bz2)    decompress="bzip2 -d -c"; ;;
-      tar)    decompress="cat"; ;;
+      gz|tgz) decompress="gzip -d -c"; unpack="untar"; ;;
+      bz2)    decompress="bzip2 -d -c"; unpack="untar"; ;;
+      tar)    decompress="untar"; unpack="untar"; ;;
+      zip)    decompress="cat"; unpack="unzipstream"; ;;
       *)
         echo "Error in www_get of URL ${url}: Unknown extension ${ext}";
         exit 1;
@@ -228,7 +287,7 @@
     if [ -n "${cache_deps}" ] && [ -n "${hash}" ]; then
       mkdir -p "${cache_deps}";
 
-      local cache_basename="${name}-$(echo "${url}" | hash)-$(basename "${url}")";
+      local cache_basename="$(echo ${name} | tr '[ ]' '_')-$(echo "${url}" | hash)-$(basename "${url}")";
       local cache_file="${cache_deps}/${cache_basename}";
 
       check_hash () {
@@ -327,7 +386,7 @@
 
     rm -rf "${path}";
     cd "$(dirname "${path}")";
-    get | ${decompress} | tar -xvf -;
+    get | ${decompress} | ${unpack};
     apply_patches "${name}" "${path}";
     cd /;
   fi;
@@ -670,12 +729,12 @@
   if type -P memcached > /dev/null; then
     using_system "memcached";
   else
-    local le="libevent-2.0.17-stable";
-    local mc="memcached-1.4.13";
-    c_dependency -m "dad64aaaaff16b5fbec25160c06fee9a" \
+    local le="libevent-2.0.21-stable";
+    local mc="memcached-1.4.15";
+    c_dependency -m "b2405cc9ebf264aa47ff615d9de527a2" \
       "libevent" "${le}" \
-      "https://github.com/downloads/libevent/libevent/${le}.tar.gz";
-    c_dependency -m "6d18c6d25da945442fcc1187b3b63b7f" \
+      "http://github.com/downloads/libevent/libevent/${le}.tar.gz";
+    c_dependency -m "36ea966f5a29655be1746bf4949f7f69" \
       "memcached" "${mc}" \
       "http://memcached.googlecode.com/files/${mc}.tar.gz";
   fi;
@@ -683,8 +742,9 @@
   if type -P postgres > /dev/null; then
     using_system "Postgres";
   else
-    local pgv="9.2.4";
-    local pg="postgresql-${pgv}";
+    local v="9.3.1";
+    local n="postgresql";
+    local p="${n}-${v}";
 
     if type -P dtrace > /dev/null; then
       local enable_dtrace="--enable-dtrace";
@@ -692,19 +752,22 @@
       local enable_dtrace="";
     fi;
 
-    c_dependency -m "52df0a9e288f02d7e6e0af89ed4dcfc6" \
-      "PostgreSQL" "${pg}" \
-      "ftp://ftp5.us.postgresql.org/pub/PostgreSQL/source/v${pgv}/${pg}.tar.gz" \
+    c_dependency -m "c003d871f712d4d3895956b028a96e74" \
+      "PostgreSQL" "${p}" \
+      "http://ftp.postgresql.org/pub/source/v${v}/${p}.tar.bz2" \
       --with-python ${enable_dtrace};
     :;
   fi;
 
-  if find_header ldap.h; then
+  if find_header ldap.h 20428 LDAP_VENDOR_VERSION; then
     using_system "OpenLDAP";
   else
-    c_dependency -m "ec63f9c2add59f323a0459128846905b" \
-      "OpenLDAP" "openldap-2.4.25" \
-      "http://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.4.25.tgz" \
+    local v="2.4.38";
+    local n="openldap";
+    local p="${n}-${v}";
+    c_dependency -m "39831848c731bcaef235a04e0d14412f" \
+      "OpenLDAP" "${p}" \
+      "http://www.openldap.org/software/download/OpenLDAP/${n}-release/${p}.tgz" \
       --disable-bdb --disable-hdb;
   fi;
 
@@ -726,24 +789,24 @@
 
   # Sourceforge mirror hostname.
   local sf="superb-sea2.dl.sourceforge.net";
-  local st="setuptools-0.6c11";
+  local st="setuptools-1.4";
   local pypi="http://pypi.python.org/packages/source";
 
-  py_dependency -m "7df2a529a074f613b509fb44feefe74e" \
+  py_dependency -v 1 -m "5710464bc5a61d75f5087f15ce63cfe0" \
     "setuptools" "setuptools" "${st}" \
     "$pypi/s/setuptools/${st}.tar.gz";
 
-  local v="4.0.3";
+  local v="4.0.5";
   local n="zope.interface";
   local p="${n}-${v}";
-  py_dependency -v 4 -m "1ddd308f2c83703accd1696158c300eb" \
+  py_dependency -v 4 -m "caf26025ae1b02da124a58340e423dfe" \
     "Zope Interface" "${n}" "${p}" \
-    "http://pypi.python.org/packages/source/z/${n}/${p}.tar.gz";
+    "http://pypi.python.org/packages/source/z/${n}/${p}.zip";
 
-  local v="0.10";
+  local v="0.12";
   local n="pyOpenSSL";
   local p="${n}-${v}";
-  py_dependency -v 0.9 -m "34db8056ec53ce80c7f5fc58bee9f093" \
+  py_dependency -v 0.12 -m "60a7bbb6160950823eddcbba2cbcb0d6" \
     "${n}" "OpenSSL" "${p}" \
     "http://pypi.python.org/packages/source/p/${n}/${p}.tar.gz";
 
@@ -754,18 +817,18 @@
       "${svn_uri_base}/${n}/trunk";
   fi;
 
-  local v="0.6.1";
+  local v="0.6.4";
   local n="xattr";
   local p="${n}-${v}";
-  py_dependency -v 0.5 -r 1038 \
-    "${n}" "${n}" "${n}" \
-    "http://svn.red-bean.com/bob/${n}/releases/${p}/";
+  py_dependency -v 0.6 -m "1bef31afb7038800f8d5cfa2f4562b37" \
+    "${n}" "${n}" "${p}" \
+    "${pypi}/x/${n}/${n}-${v}.tar.gz";
 
   if [ -n "${ORACLE_HOME:-}" ]; then
-    local v="5.1";
+    local v="5.1.2";
     local n="cx_Oracle";
     local p="${n}-${v}";
-    py_dependency -v "${v}" -m "d2697493a40c9d46c9b7c1c210b61671" \
+    py_dependency -v "${v}" -m "462f309e00f7bff7100e2077fc43172c" \
       "${n}" "${n}" "${p}" \
       "http://${sf}/project/cx-oracle/${v}/${p}.tar.gz";
   fi;
@@ -779,10 +842,10 @@
 
   # Maintenance note: next time the Twisted dependency gets updated, check out
   # twext/patches.py.
-  local v="12.3.0";
+  local v="13.2.0";
   local n="Twisted";
   local p="${n}-${v}";
-  py_dependency -v 12.2 -m "6e289825f3bf5591cfd670874cc0862d" \
+  py_dependency -v 13.2 -m "83fe6c0c911cc1602dbffb036be0ba79" \
     "${n}" "twisted" "${p}" \
     "${pypi}/T/${n}/${p}.tar.bz2";
 
@@ -793,22 +856,22 @@
     "${n}" "dateutil" "${p}" \
     "http://www.labix.org/download/${n}/${p}.tar.gz";
 
-  local v="0.6.1";
+  local v="1.2.0";
   local n="psutil";
   local p="${n}-${v}";
-  py_dependency -m "3cfcbfb8525f6e4c70110e44a85e907e" \
+  py_dependency -m "f8ae906249e65db21f17d873ae07e584" \
     "${n}" "${n}" "${p}" \
-    "http://${n}.googlecode.com/files/${p}.tar.gz";
+    "${pypi}/p/${n}/${p}.tar.gz";
 
-  local v="2.3.13";
+  local v="2.4.13";
   local n="python-ldap";
   local p="${n}-${v}";
-  py_dependency -v "${v}" -m "895223d32fa10bbc29aa349bfad59175" \
+  py_dependency -v "${v}" -m "74b7b50267761540451eade44b2049ee" \
     "Python-LDAP" "ldap" "${p}" \
     "${pypi}/p/${n}/${p}.tar.gz";
 
   # XXX actually PyCalendar should be imported in-place.
-  py_dependency -fe -i "src" -r 11914 \
+  py_dependency -fe -i "src" -r 11947 \
     "PyCalendar" "pycalendar" "pycalendar" \
     "${svn_uri_base}/PyCalendar/trunk";
 
@@ -840,44 +903,45 @@
     "${svn_uri_base}/CalDAVClientLibrary/trunk";
 
   # Can't add "-v 2011g" to args because the version check expects numbers.
+  local v="2013.8";
   local n="pytz";
-  local p="${n}-2011n";
-  py_dependency -m "75ffdc113a4bcca8096ab953df746391" \
+  local p="${n}-${v}";
+  py_dependency -m "37750ca749ed3a52523b9682b0b7e381" \
     "${n}" "${n}" "${p}" \
     "${pypi}/p/${n}/${p}.tar.gz";
 
-  local v="2.5";
+  local v="2.6.1";
   local n="pycrypto";
   local p="${n}-${v}";
-  py_dependency -v "${v}" -m "783e45d4a1a309e03ab378b00f97b291" \
+  py_dependency -v "${v}" -m "55a61a054aa66812daf5161a0d5d7eda" \
     "PyCrypto" "${n}" "${p}" \
     "http://ftp.dlitz.net/pub/dlitz/crypto/${n}/${p}.tar.gz";
 
-  local v="0.1.2";
+  local v="0.1.7";
   local n="pyasn1";
   local p="${n}-${v}";
-  py_dependency -v "${v}" -m "a7c67f5880a16a347a4d3ce445862a47" \
+  py_dependency -v "${v}" -m "2cbd80fcd4c7b1c82180d3d76fee18c8" \
     "${n}" "${n}" "${p}" \
     "${pypi}/p/${n}/${p}.tar.gz";
 
-  local v="1.1.6";
+  local v="1.1.8";
   local n="setproctitle";
   local p="${n}-${v}";
-  py_dependency -v "1.0" -m "1e42e43b440214b971f4b33c21eac369" \
+  py_dependency -v "1.0" -m "728f4c8c6031bbe56083a48594027edd" \
     "${n}" "${n}" "${p}" \
     "${pypi}/s/${n}/${p}.tar.gz";
 
-  local v="0.6";
+  local v="0.8";
   local n="cffi";
   local p="${n}-${v}";
-  py_dependency -v "0.6" -m "5be33b1ab0247a984d42b27344519337" \
+  py_dependency -v "0.6" -m "e61deb0515311bb42d5d58b9403bc923" \
     "${n}" "${n}" "${p}" \
     "${pypi}/c/${n}/${p}.tar.gz";
 
-  local v="2.09.1";
+  local v="2.10";
   local n="pycparser";
   local p="${n}-${v}";
-  py_dependency -v "0.6" -m "74aa075fc28b7c24a4426574d1ac91e0" \
+  py_dependency -v "0.6" -m "d87aed98c8a9f386aa56d365fe4d515f" \
     "${n}" "${n}" "${p}" \
     "${pypi}/p/${n}/${p}.tar.gz";
 
@@ -889,21 +953,21 @@
   local p="${n}-${v}";
   py_dependency -o -m "36407974bd5da2af00bf90ca27feeb44" \
     "Epydoc" "${n}" "${p}" \
-    "https://pypi.python.org/packages/source/e/${n}/${p}.tar.gz";
+    "${pypi}/e/${n}/${p}.tar.gz";
 
   local v="0.10.0";
   local n="Nevow";
   local p="${n}-${v}";
   py_dependency -o -m "66dda2ad88f42dea05911add15f4d1b2" \
     "${n}" "${n}" "${p}" \
-    "https://pypi.python.org/packages/source/N/${n}/${p}.tar.gz";
+    "${pypi}/N/${n}/${p}.tar.gz";
 
-  local v="0.4";
+  local v="0.5b1";
   local n="pydoctor";
   local p="${n}-${v}";
-  py_dependency -o -m "b7564e12b5d35d4cb529a2c220b25d3a" \
+  py_dependency -o -m "c4fb33672f37624116cc7a0606f74f28" \
     "${n}" "${n}" "${p}" \
-    "https://pypi.python.org/packages/source/p/${n}/${p}.tar.gz";
+    "{$pypi}/p/${n}/${p}.tar.gz";
 
   if "${do_setup}"; then
     cd "${caldav}";

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/version.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/version.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/support/version.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -26,7 +26,7 @@
     # Compute the version number.
     #
 
-    base_version = "5.2"
+    base_version = "6.0"
 
     branches = tuple(
         branch.format(version=base_version)

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/test
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/test	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/test	2013-12-02 19:04:21 UTC (rev 12016)
@@ -21,7 +21,7 @@
 
 wd="$(cd "$(dirname "$0")" && pwd -L)";
 
-. "${wd}/support/build.sh";
+#. "${wd}/support/build.sh";
 
 do_setup="false";
 do_get="false";
@@ -74,9 +74,6 @@
 
 export PYTHONPATH="${wd}:${PYTHONPATH:-}";
 
-dependencies;
-trial="$(type -p trial)";
-
 if [ $# -gt 0 ]; then
   test_modules="$@";
   flaky=true;
@@ -88,7 +85,7 @@
 find "${wd}" -name \*.pyc -print0 | xargs -0 rm;
 
 mkdir -p "${wd}/data";
-cd "${wd}" && "${python}" "${trial}" --temp-directory="${wd}/data/trial" --rterrors ${reactor} ${random} ${until_fail} ${no_colour} ${coverage} ${numjobs} ${test_modules};
+cd "${wd}" && "${wd}/bin/trial" --temp-directory="${wd}/data/trial" --rterrors ${reactor} ${random} ${until_fail} ${no_colour} ${coverage} ${numjobs} ${test_modules};
 
 if ${flaky}; then
   echo "";

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/internet/sendfdport.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/internet/sendfdport.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/internet/sendfdport.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -94,8 +94,8 @@
     A socket in the master process pointing at a file descriptor that can be
     used to transmit sockets to a subprocess.
 
-    @ivar skt: the UNIX socket used as the sendmsg() transport.
-    @type skt: L{socket.socket}
+    @ivar outSocket: the UNIX socket used as the sendmsg() transport.
+    @type outSocket: L{socket.socket}
 
     @ivar outgoingSocketQueue: an outgoing queue of sockets to send to the
         subprocess, along with their descriptions (strings describing their
@@ -115,12 +115,13 @@
     @type dispatcher: L{InheritedSocketDispatcher}
     """
 
-    def __init__(self, dispatcher, skt, status):
+    def __init__(self, dispatcher, inSocket, outSocket, status):
         FileDescriptor.__init__(self, dispatcher.reactor)
         self.status = status
         self.dispatcher = dispatcher
-        self.skt = skt          # XXX needs to be set non-blocking by somebody
-        self.fileno = skt.fileno
+        self.inSocket = inSocket
+        self.outSocket = outSocket   # XXX needs to be set non-blocking by somebody
+        self.fileno = outSocket.fileno
         self.outgoingSocketQueue = []
         self.pendingCloseSocketQueue = []
 
@@ -138,7 +139,7 @@
         Receive a status / health message and record it.
         """
         try:
-            data, _ignore_flags, _ignore_ancillary = recvmsg(self.skt.fileno())
+            data, _ignore_flags, _ignore_ancillary = recvmsg(self.outSocket.fileno())
         except SocketError, se:
             if se.errno not in (EAGAIN, ENOBUFS):
                 raise
@@ -155,7 +156,7 @@
         while self.outgoingSocketQueue:
             skt, desc = self.outgoingSocketQueue.pop(0)
             try:
-                sendfd(self.skt.fileno(), skt.fileno(), desc)
+                sendfd(self.outSocket.fileno(), skt.fileno(), desc)
             except SocketError, se:
                 if se.errno in (EAGAIN, ENOBUFS):
                     self.outgoingSocketQueue.insert(0, (skt, desc))
@@ -341,14 +342,27 @@
         i, o = socketpair()
         i.setblocking(False)
         o.setblocking(False)
-        a = _SubprocessSocket(self, o, self.statusWatcher.initialStatus())
+        a = _SubprocessSocket(self, i, o, self.statusWatcher.initialStatus())
         self._subprocessSockets.append(a)
         if self._isDispatching:
             a.startReading()
         return i
 
 
+    def removeSocket(self, skt):
+        """
+        Removes a previously added socket from the pool of sockets being used
+        for transmitting file descriptors to child processes.
+        """
+        for a in self._subprocessSockets:
+            if a.inSocket == skt:
+                self._subprocessSockets.remove(a)
+                break
+        else:
+            raise ValueError("Unknown socket: {0}".format(skt))
 
+
+
 class InheritedPort(FileDescriptor, object):
     """
     An L{InheritedPort} is an L{IReadDescriptor}/L{IWriteDescriptor} created in

Copied: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/protocols/echo.py (from rev 12014, CalendarServer/trunk/twext/protocols/echo.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/protocols/echo.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/protocols/echo.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -0,0 +1,35 @@
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Echo protocol.
+"""
+
+__all__ = ["EchoProtocol"]
+
+from twisted.internet.protocol import Protocol
+
+
+class EchoProtocol(Protocol):
+    """
+    Say what you hear.
+    """
+
+    def dataReceived(self, data):
+        """
+        As soon as any data is received, write it back.
+        """
+        self.transport.write(data)

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/aggregate.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/aggregate.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/aggregate.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -30,8 +30,8 @@
 
 from twext.who.idirectory import DirectoryConfigurationError
 from twext.who.idirectory import IDirectoryService
-from twext.who.index import DirectoryService as BaseDirectoryService
-from twext.who.index import DirectoryRecord
+from twext.who.directory import DirectoryService as BaseDirectoryService
+from twext.who.directory import DirectoryRecord
 from twext.who.util import ConstantsContainer
 
 

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/directory.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/directory.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -41,7 +41,7 @@
 @implementer(IDirectoryService)
 class DirectoryService(object):
     """
-    Generic implementation of L{IDirectoryService}.
+    Generic (and abstract) implementation of L{IDirectoryService}.
 
     Most of the C{recordsWith*} methods call L{recordsWithFieldValue}, which in
     turn calls L{recordsFromExpression} with a corresponding

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/index.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/index.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/index.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -30,7 +30,7 @@
 from twisted.internet.defer import succeed, inlineCallbacks, returnValue
 
 from twext.who.util import ConstantsContainer
-from twext.who.util import describe, uniqueResult
+from twext.who.util import uniqueResult
 from twext.who.idirectory import FieldName as BaseFieldName
 from twext.who.expression import MatchExpression, MatchType, MatchFlags
 from twext.who.directory import DirectoryService as BaseDirectoryService
@@ -55,7 +55,91 @@
 
 class DirectoryService(BaseDirectoryService):
     """
-    XML directory service.
+    Generic (and abstract) in-memory-indexed directory service.
+
+    This class implements the record access API in L{BaseDirectoryService} by
+    caching all records in an in-memory dictionary.
+
+    Each indexed field has a top-level key in the index and in turn contains
+    a dictionary in which keys are field values, and values are directory
+    records which have a matching field value for the cooresponding key::
+
+        {
+            <FieldName1>: {
+                <value1a>: set([<record1a1>, ...]),
+                ...
+            },
+            ...
+        }
+
+    Here is an example index for a service with a three user records and one
+    group record::
+
+        {
+            <FieldName=uid>: {
+                u'__calendar-dev__': set([
+                    <DirectoryRecord (group)calendar-dev>
+                ]),
+                u'__dre__': set([
+                    <DirectoryRecord (user)dre>
+                ]),
+                u'__sagen__': set([
+                    <DirectoryRecord (user)sagen>
+                ]),
+                u'__wsanchez__': set([
+                    <DirectoryRecord (user)wsanchez>
+                ])
+            },
+            <FieldName=recordType>: {
+                <RecordType=group>: set([
+                    <DirectoryRecord (group)calendar-dev>,
+                ]),
+                <RecordType=user>: set([
+                    <DirectoryRecord (user)sagen>,
+                    <DirectoryRecord (user)wsanchez>
+                ])
+            },
+            <FieldName=shortNames>: {
+                u'calendar-dev': set([<DirectoryRecord (group)calendar-dev>]),
+                u'dre': set([<DirectoryRecord (user)dre>]),
+                u'sagen': set([<DirectoryRecord (user)sagen>]),
+                u'wilfredo_sanchez': set([<DirectoryRecord (user)wsanchez>]),
+                u'wsanchez': set([<DirectoryRecord (user)wsanchez>])
+            },
+            <FieldName=emailAddresses>: {
+                'dev at bitbucket.calendarserver.org': set([
+                    <DirectoryRecord (group)calendar-dev>
+                ]),
+                'dre at bitbucket.calendarserver.org': set([
+                    <DirectoryRecord (user)dre>
+                ]),
+                'sagen at bitbucket.calendarserver.org': set([
+                    <DirectoryRecord (user)sagen>
+                ]),
+                'shared at example.com': set([
+                    <DirectoryRecord (user)sagen>,
+                    <DirectoryRecord (user)dre>
+                ]),
+                'wsanchez at bitbucket.calendarserver.org': set([
+                    <DirectoryRecord (user)wsanchez>
+                ]),
+                'wsanchez at devnull.twistedmatrix.com': set([
+                    <DirectoryRecord (user)wsanchez>
+                ])
+            },
+            <FieldName=memberUIDs>: {
+                u'__sagen__': set([<DirectoryRecord (group)calendar-dev>]),
+                u'__wsanchez__': set([<DirectoryRecord (group)calendar-dev>])
+            }
+        }
+
+    The field names that are indexed are defined by the C{indexedFields}
+    attribute of the service.
+
+    A subclass must override L{loadRecords}, which populates the index.
+
+    @cvar indexedFields: an iterable of field names (C{NamedConstant})
+        which are indexed.
     """
 
     fieldName = ConstantsContainer(chain(
@@ -82,43 +166,74 @@
     @property
     def index(self):
         """
-        Call L{loadRecords}C{()} and return the index.
+        Call L{loadRecords} and return the index.
         """
         self.loadRecords()
         return self._index
 
 
-    @index.setter
-    def index(self, value):
+    def loadRecords(self):
         """
-        Sets the index.
+        Load records.  This method is called by the L{index} property and
+        provides a hook into which the index can be updated.
 
-        @param index: An index.
-        @type index: L{dict}
+        This method must be implemented by subclasses.
+
+        An example implementation::
+
+            def loadRecords(self):
+                self.flush()
+                while True:
+                    records = readSomeRecordsFromMyBackEnd()
+                    if not records:
+                        break
+                    self.indexRecords(records)
         """
-        self._index = value
+        raise NotImplementedError("Subclasses must implement loadRecords().")
 
 
-    def loadRecords(self):
+    def indexRecords(self, records):
         """
-        Load records.
+        Add some records to the index.
+
+        @param records: The records to index.
+        @type records: iterable of L{DirectoryRecord}
         """
-        raise NotImplementedError("Subclasses must implement loadRecords().")
+        index = self._index
 
+        for fieldName in self.indexedFields:
+            index.setdefault(fieldName, {})
 
+        for record in records:
+            for fieldName in self.indexedFields:
+                values = record.fields.get(fieldName, None)
+
+                if values is not None:
+                    if not BaseFieldName.isMultiValue(fieldName):
+                        values = (values,)
+
+                    for value in values:
+                        index[fieldName].setdefault(value, set()).add(record)
+
+
     def flush(self):
         """
         Flush the index.
         """
-        self._index = None
+        index = {}
 
+        for fieldName in self.indexedFields:
+            index.setdefault(fieldName, {})
 
+        self._index = index
+
+
     def indexedRecordsFromMatchExpression(self, expression, records=None):
         """
         Finds records in the internal indexes matching a single expression.
 
         @param expression: An expression.
-        @type expression: L{object}
+        @type expression: L{MatchExpression}
 
         @param records: a set of records to limit the search to. C{None} if
             the whole directory should be searched.
@@ -130,7 +245,15 @@
         predicate = MatchFlags.predicator(expression.flags)
         normalize = MatchFlags.normalizer(expression.flags)
 
-        fieldIndex = self.index[expression.fieldName]
+        try:
+            fieldIndex = self.index[expression.fieldName]
+        except KeyError:
+            raise TypeError(
+                "indexedRecordsFromMatchExpression() was passed an "
+                "expression with an unindexed field: {0!r}"
+                .format(expression.fieldName)
+            )
+
         matchValue = normalize(expression.fieldValue)
         matchType  = expression.matchType
 
@@ -154,7 +277,7 @@
                 )
         else:
             raise NotImplementedError(
-                "Unknown match type: {0}".format(describe(matchType))
+                "Unknown match type: {0!r}".format(matchType)
             )
 
         matchingRecords = set()
@@ -173,7 +296,7 @@
         Finds records not in the internal indexes matching a single expression.
 
         @param expression: An expression.
-        @type expression: L{object}
+        @type expression: L{MatchExpression}
 
         @param records: a set of records to limit the search to. C{None} if
             the whole directory should be searched.
@@ -198,7 +321,7 @@
             match = lambda fieldValue: predicate(fieldValue == matchValue)
         else:
             raise NotImplementedError(
-                "Unknown match type: {0}".format(describe(matchType))
+                "Unknown match type: {0!r}".format(matchType)
             )
 
         result = set()
@@ -223,6 +346,10 @@
 
 
     def recordsFromNonCompoundExpression(self, expression, records=None):
+        """
+        This implementation can handle L{MatchExpression} expressions; other
+        expressions are passed up to the superclass.
+        """
         if isinstance(expression, MatchExpression):
             if expression.fieldName in self.indexedFields:
                 return self.indexedRecordsFromMatchExpression(

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_aggregate.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_aggregate.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_aggregate.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -22,15 +22,16 @@
 from twisted.trial import unittest
 
 from twext.who.idirectory import IDirectoryService, DirectoryConfigurationError
-from twext.who.aggregate import DirectoryService
+from twext.who.aggregate import DirectoryService, DirectoryRecord
 from twext.who.util import ConstantsContainer
-
 from twext.who.test import test_directory, test_xml
-from twext.who.test.test_xml import QueryMixIn, xmlService
-from twext.who.test.test_xml import TestService as XMLTestService
+from twext.who.test.test_xml import (
+    QueryMixIn, xmlService,
+    TestService as XMLTestService,
+    DirectoryServiceConvenienceTestMixIn
+)
 
 
-
 class BaseTest(object):
     def service(self, services=None):
         if services is None:
@@ -53,11 +54,23 @@
 
 
     def xmlService(self, xmlData=None, serviceClass=None):
-        return xmlService(self.mktemp(), xmlData, serviceClass)
+        return xmlService(
+            self.mktemp(),
+            xmlData=xmlData,
+            serviceClass=serviceClass
+        )
 
 
 
-class DirectoryServiceBaseTest(BaseTest, test_xml.DirectoryServiceBaseTest):
+class DirectoryServiceTest(
+    unittest.TestCase,
+    BaseTest,
+    DirectoryServiceConvenienceTestMixIn,
+    test_directory.BaseDirectoryServiceTest
+):
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
     def test_repr(self):
         service = self.service()
         self.assertEquals(repr(service), "<TestService u'xyzzy'>")
@@ -73,7 +86,8 @@
     BaseTest,
     test_directory.BaseDirectoryServiceImmutableTest,
 ):
-    pass
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
 
 
 
@@ -86,21 +100,24 @@
             recordType = ConstantsContainer((XMLTestService.recordType.group,))
 
         usersService = self.xmlService(
-            testXMLConfigUsers,
-            UsersDirectoryService
+            xmlData=testXMLConfigUsers,
+            serviceClass=UsersDirectoryService
         )
         groupsService = self.xmlService(
-            testXMLConfigGroups,
-            GroupsDirectoryService
+            xmlData=testXMLConfigGroups,
+            serviceClass=GroupsDirectoryService
         )
 
-        return BaseTest.service(self, (usersService, groupsService))
+        return BaseTest.service(
+            self,
+            services=(usersService, groupsService)
+        )
 
 
 
 class DirectoryServiceAggregatedBaseTest(
     AggregatedBaseTest,
-    DirectoryServiceBaseTest,
+    DirectoryServiceTest,
 ):
     pass
 
@@ -126,8 +143,8 @@
     def test_conflictingRecordTypes(self):
         self.assertRaises(
             DirectoryConfigurationError,
-            BaseTest.service, self,
-            (self.xmlService(), self.xmlService(testXMLConfigUsers)),
+            self.service,
+            services=(self.xmlService(), self.xmlService(testXMLConfigUsers)),
         )
 
 

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_directory.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_directory.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -25,7 +25,6 @@
 
 from twisted.python.constants import Names, NamedConstant
 from twisted.trial import unittest
-from twisted.trial.unittest import SkipTest
 from twisted.internet.defer import inlineCallbacks
 from twisted.internet.defer import succeed
 
@@ -36,17 +35,67 @@
 from twext.who.directory import DirectoryService, DirectoryRecord
 
 
+
+class StubDirectoryService(DirectoryService):
+    """
+    Stub directory service with some built-in records and an implementation
+    of C{recordsFromNonCompoundExpression}.
+    """
+
+    def __init__(self, realmName):
+        DirectoryService.__init__(self, realmName)
+
+        self.records = RecordStorage(self, DirectoryRecord)
+
+
+    def recordsFromExpression(self, expression):
+        self.seenExpressions = []
+
+        return DirectoryService.recordsFromExpression(self, expression)
+
+
+    def recordsFromNonCompoundExpression(self, expression, records=None):
+        """
+        This implementation handles three expressions:
+
+        The expression C{u"None"} will match no records.
+
+        The expressions C{u"twistedmatrix.com"} and C{u"calendarserver.org"}
+        will match records that have an email address ending with the
+        given expression.
+        """
+        self.seenExpressions.append(expression)
+
+        if expression == u"None":
+            return succeed([])
+
+        if expression in (u"twistedmatrix.com", u"calendarserver.org"):
+            result = []
+            for record in self.records:
+                for email in record.emailAddresses:
+                    if email.endswith(expression):
+                        result.append(record)
+                        break
+            return succeed(result)
+
+        return DirectoryService.recordsFromNonCompoundExpression(
+            self, expression, records=records
+        )
+
+
+
 class ServiceMixIn(object):
     """
     MixIn that sets up a service appropriate for testing.
     """
+
     realmName = u"xyzzy"
 
 
-    def service(self):
-        if not hasattr(self, "_service"):
-            self._service = DirectoryService(self.realmName)
-        return self._service
+    def service(self, subClass=None):
+        if subClass is None:
+            subClass = self.serviceClass
+        return subClass(self.realmName)
 
 
 
@@ -76,17 +125,20 @@
 
     def test_repr(self):
         """
-        C{repr} returns the expected string.
+        L{DirectoryService.repr} returns the expected string.
         """
         service = self.service()
-        self.assertEquals(repr(service), "<DirectoryService u'xyzzy'>")
+        self.assertEquals(
+            repr(service),
+            "<{0} u'xyzzy'>".format(self.serviceClass.__name__)
+        )
 
 
     def test_recordTypes(self):
         """
-        C{recordTypes} returns the supported set of record types.
-        For L{DirectoryService}, that's the set of constants in the
-        C{recordType} attribute.
+        L{DirectoryService.recordTypes} returns the supported set of record
+        types. For L{DirectoryService}, that's the set of constants in the
+        L{DirectoryService.recordType} attribute.
         """
         service = self.service()
         self.assertEquals(
@@ -97,8 +149,8 @@
 
     def test_recordsFromNonCompoundExpression_unknownExpression(self):
         """
-        C{recordsFromNonCompoundExpression} with an unknown expression type
-        fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordsFromNonCompoundExpression} with an unknown
+        expression type fails with L{QueryNotSupportedError}.
         """
         service = self.service()
         self.assertFailure(
@@ -110,8 +162,8 @@
     @inlineCallbacks
     def test_recordsFromNonCompoundExpression_emptyRecords(self):
         """
-        C{recordsFromNonCompoundExpression} with an unknown expression type
-        and an empty C{records} set returns an empty result.
+        L{DirectoryService.recordsFromNonCompoundExpression} with an unknown
+        expression type and an empty C{records} set returns an empty result.
         """
         service = self.service()
         result = (
@@ -124,12 +176,13 @@
 
     def test_recordsFromNonCompoundExpression_nonEmptyRecords(self):
         """
-        C{recordsFromNonCompoundExpression} with an unknown expression type
-        and a non-empty C{records} fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordsFromNonCompoundExpression} with an unknown
+        expression type and a non-empty C{records} fails with
+        L{QueryNotSupportedError}.
         """
         service = self.service()
 
-        wsanchez = DirectoryRecord(
+        wsanchez = self.directoryRecordClass(
             service,
             {
                 service.fieldName.recordType: service.recordType.user,
@@ -148,8 +201,8 @@
 
     def test_recordsFromExpression_unknownExpression(self):
         """
-        C{recordsFromExpression} with an unknown expression type fails with
-        L{QueryNotSupportedError}.
+        L{DirectoryService.recordsFromExpression} with an unknown expression
+        type fails with L{QueryNotSupportedError}.
         """
         service = self.service()
         result = yield(service.recordsFromExpression(object()))
@@ -159,8 +212,8 @@
     @inlineCallbacks
     def test_recordsFromExpression_emptyExpression(self):
         """
-        C{recordsFromExpression} with an unknown expression type and an empty
-        L{CompoundExpression} returns an empty result.
+        L{DirectoryService.recordsFromExpression} with an unknown expression
+        type and an empty L{CompoundExpression} returns an empty result.
         """
         service = self.service()
 
@@ -204,13 +257,22 @@
 
 
 
-class DirectoryServiceTest(unittest.TestCase, BaseDirectoryServiceTest):
+class DirectoryServiceRecordsFromExpressionTest(
+    unittest.TestCase,
+    ServiceMixIn
+):
+    """
+    Tests for L{DirectoryService.recordsFromExpression}.
+    """
+    serviceClass = StubDirectoryService
+    directoryRecordClass = DirectoryRecord
+
     @inlineCallbacks
     def test_recordsFromExpression_single(self):
         """
-        C{recordsFromExpression} handles a single expression
+        L{DirectoryService.recordsFromExpression} handles a single expression.
         """
-        service = StubDirectoryService()
+        service = self.service()
 
         result = yield service.recordsFromExpression("twistedmatrix.com")
 
@@ -228,10 +290,10 @@
     @inlineCallbacks
     def test_recordsFromExpression_OR(self):
         """
-        C{recordsFromExpression} handles a L{CompoundExpression} with
-        L{Operand.OR}.
+        L{DirectoryService.recordsFromExpression} handles a
+        L{CompoundExpression} with L{Operand.OR}.
         """
-        service = StubDirectoryService()
+        service = self.service()
 
         result = yield service.recordsFromExpression(
             CompoundExpression(
@@ -260,10 +322,10 @@
     @inlineCallbacks
     def test_recordsFromExpression_AND(self):
         """
-        C{recordsFromExpression} handles a L{CompoundExpression} with
-        L{Operand.AND}.
+        L{DirectoryService.recordsFromExpression} handles a
+        L{CompoundExpression} with L{Operand.AND}.
         """
-        service = StubDirectoryService()
+        service = self.service()
 
         result = yield service.recordsFromExpression(
             CompoundExpression(
@@ -287,11 +349,11 @@
     @inlineCallbacks
     def test_recordsFromExpression_AND_optimized(self):
         """
-        C{recordsFromExpression} handles a L{CompoundExpression} with
-        L{Operand.AND}, and when one of the expression matches no records, the
-        subsequent expressions are skipped.
+        L{DirectoryService.recordsFromExpression} handles a
+        L{CompoundExpression} with L{Operand.AND}, and when one of the
+        expression matches no records, the subsequent expressions are skipped.
         """
-        service = StubDirectoryService()
+        service = self.service()
 
         result = yield service.recordsFromExpression(
             CompoundExpression(
@@ -317,10 +379,11 @@
 
     def test_recordsFromExpression_unknownOperand(self):
         """
-        C{recordsFromExpression} fails with L{QueryNotSupportedError} when
-        given a L{CompoundExpression} with an unknown operand.
+        L{DirectoryService.recordsFromExpression} fails with
+        L{QueryNotSupportedError} when given a L{CompoundExpression} with an
+        unknown operand.
         """
-        service = StubDirectoryService()
+        service = self.service()
 
         results = service.recordsFromExpression(
             CompoundExpression(
@@ -335,9 +398,21 @@
         self.assertFailure(results, QueryNotSupportedError)
 
 
+
+class DirectoryServiceConvenienceTest(
+    unittest.TestCase,
+    BaseDirectoryServiceTest
+):
+    """
+    Tests for L{DirectoryService} convenience methods.
+    """
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
+
     def test_recordWithUID(self):
         """
-        C{recordWithUID} fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordWithUID} fails with L{QueryNotSupportedError}.
         """
         service = self.service()
 
@@ -349,7 +424,8 @@
 
     def test_recordWithGUID(self):
         """
-        C{recordWithGUID} fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordWithGUID} fails with
+        L{QueryNotSupportedError}.
         """
         service = self.service()
 
@@ -361,7 +437,8 @@
 
     def test_recordsWithRecordType(self):
         """
-        C{recordsWithRecordType} fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordsWithRecordType} fails with
+        L{QueryNotSupportedError}.
         """
         service = self.service()
 
@@ -374,7 +451,8 @@
 
     def test_recordWithShortName(self):
         """
-        C{recordWithShortName} fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordWithShortName} fails with
+        L{QueryNotSupportedError}.
         """
         service = self.service()
 
@@ -387,7 +465,8 @@
 
     def test_recordsWithEmailAddress(self):
         """
-        C{recordsWithEmailAddress} fails with L{QueryNotSupportedError}.
+        L{DirectoryService.recordsWithEmailAddress} fails with
+        L{QueryNotSupportedError}.
         """
         service = self.service()
 
@@ -400,7 +479,7 @@
 
 class BaseDirectoryServiceImmutableTest(ServiceMixIn):
     """
-    Immutable directory record tests.
+    Tests for immutable directory services.
     """
 
     def test_updateRecordsNotAllowed(self):
@@ -409,7 +488,7 @@
         """
         service = self.service()
 
-        newRecord = DirectoryRecord(
+        newRecord = self.directoryRecordClass(
             service,
             fields={
                 service.fieldName.uid: u"__plugh__",
@@ -442,11 +521,19 @@
     unittest.TestCase,
     BaseDirectoryServiceImmutableTest,
 ):
-    pass
+    """
+    Tests for immutable L{DirectoryService}.
+    """
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
 
 
 
 class BaseDirectoryRecordTest(ServiceMixIn):
+    """
+    Tests for directory records.
+    """
+
     fields_wsanchez = {
         FieldName.uid: u"UID:wsanchez",
         FieldName.recordType: RecordType.user,
@@ -604,7 +691,7 @@
 
     def test_repr(self):
         """
-        C{repr} returns the expected string.
+        L{DirectoryRecord.repr} returns the expected string.
         """
         wsanchez = self.makeRecord(self.fields_wsanchez)
 
@@ -667,7 +754,7 @@
 
     def test_description(self):
         """
-        C{description} returns the expected string.
+        L{DirectoryRecord.description} returns the expected string.
         """
         sagen = self.makeRecord(self.fields_sagen)
 
@@ -685,18 +772,20 @@
             sagen.description()
         )
 
+    test_description.todo = "Intermittent order issues"
 
+
     def test_members_group(self):
         """
-        Group members.
+        Group members for group records.
         """
-        raise SkipTest("Subclasses should implement this test.")
+        raise NotImplementedError("Subclasses should implement this test.")
 
 
     @inlineCallbacks
     def test_members_nonGroup(self):
         """
-        Non-groups have no members.
+        Group members for non-group records.  Non-groups have no members.
         """
         wsanchez = self.makeRecord(self.fields_wsanchez)
 
@@ -706,55 +795,64 @@
         )
 
 
-    def test_groups(self):
+    def test_memberships(self):
         """
         Group memberships.
         """
-        raise SkipTest("Subclasses should implement this test.")
+        raise NotImplementedError("Subclasses should implement this test.")
 
 
 
 class DirectoryRecordTest(unittest.TestCase, BaseDirectoryRecordTest):
+    """
+    Tests for L{DirectoryRecord}.
+    """
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
     def test_members_group(self):
         staff = self.makeRecord(self.fields_staff)
 
         self.assertFailure(staff.members(), NotImplementedError)
 
 
-    def test_groups(self):
+    def test_memberships(self):
         wsanchez = self.makeRecord(self.fields_wsanchez)
 
         self.assertFailure(wsanchez.groups(), NotImplementedError)
 
 
 
-class StubDirectoryService(DirectoryService):
+class RecordStorage(object):
     """
-    Stubn directory service with some built-in records and an implementation
-    of C{recordsFromNonCompoundExpression}.
+    Container for directory records.
     """
+    def __init__(self, service, recordClass):
+        self.service = service
+        self.recordClass = recordClass
+        self.records = []
 
-    def __init__(self):
-        DirectoryService.__init__(self, u"Stub")
+        self.addDefaultRecords()
 
-        self.records = []
-        self._addRecords()
 
-
-    def _addRecords(self):
+    def addDefaultRecords(self):
         """
         Add a known set of records to this service.
         """
-        self._addUser(
+        self.addUser(
             shortNames=[u"wsanchez", u"wilfredo_sanchez"],
-            fullNames=[u"Wilfredo S\xe1nchez Vega"],
+            fullNames=[
+                u"Wilfredo S\xe1nchez Vega",
+                u"Wilfredo Sanchez Vega",
+                u"Wilfredo Sanchez",
+            ],
             emailAddresses=[
                 u"wsanchez at bitbucket.calendarserver.org",
                 u"wsanchez at devnull.twistedmatrix.com",
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"glyph"],
             fullNames=[u"Glyph Lefkowitz"],
             emailAddresses=[
@@ -763,7 +861,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"sagen"],
             fullNames=[u"Morgen Sagen"],
             emailAddresses=[
@@ -772,7 +870,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"cdaboo"],
             fullNames=[u"Cyrus Daboo"],
             emailAddresses=[
@@ -780,7 +878,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"dre"],
             fullNames=[u"Andre LaBranche"],
             emailAddresses=[
@@ -789,7 +887,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"exarkun"],
             fullNames=[u"Jean-Paul Calderone"],
             emailAddresses=[
@@ -797,7 +895,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"dreid"],
             fullNames=[u"David Reid"],
             emailAddresses=[
@@ -805,7 +903,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"joe"],
             fullNames=[u"Joe Schmoe"],
             emailAddresses=[
@@ -813,7 +911,7 @@
             ],
         )
 
-        self._addUser(
+        self.addUser(
             shortNames=[u"alyssa"],
             fullNames=[u"Alyssa P. Hacker"],
             emailAddresses=[
@@ -822,7 +920,7 @@
         )
 
 
-    def _addUser(self, shortNames, fullNames, emailAddresses=[]):
+    def addUser(self, shortNames, fullNames, emailAddresses=[]):
         """
         Add a user record with the given field information.
 
@@ -835,42 +933,24 @@
         @param emailAddresses: Record email addresses.
         @type emailAddresses: L{list} of L{unicode}s
         """
-        self.records.append(DirectoryRecord(self, {
-            self.fieldName.recordType: self.recordType.user,
-            self.fieldName.uid: u"__{0}__".format(shortNames[0]),
-            self.fieldName.shortNames: shortNames,
-            self.fieldName.fullNames: fullNames,
-            self.fieldName.password: u"".join(reversed(shortNames[0])),
-            self.fieldName.emailAddresses: emailAddresses,
+        service = self.service
+        fieldName = service.fieldName
+        recordType = service.recordType
+        self.records.append(self.recordClass(self.service, {
+            fieldName.recordType: recordType.user,
+            fieldName.uid: u"__{0}__".format(shortNames[0]),
+            fieldName.shortNames: shortNames,
+            fieldName.fullNames: fullNames,
+            fieldName.password: u"".join(reversed(shortNames[0])),
+            fieldName.emailAddresses: emailAddresses,
         }))
 
 
-    def recordsFromExpression(self, expression):
-        self.seenExpressions = []
+    def __iter__(self):
+        return iter(self.records)
 
-        return DirectoryService.recordsFromExpression(self, expression)
 
 
-    def recordsFromNonCompoundExpression(self, expression, records=None):
-        self.seenExpressions.append(expression)
-
-        if expression == u"None":
-            return succeed([])
-
-        if expression in (u"twistedmatrix.com", u"calendarserver.org"):
-            result = []
-            for record in self.records:
-                for email in record.emailAddresses:
-                    if email.endswith(expression):
-                        result.append(record)
-                        break
-            return succeed(result)
-
-        return DirectoryService.recordsFromNonCompoundExpression(
-            self, expression, records=records
-        )
-
-
 class WackyOperand(Names):
     """
     Wacky operands.

Copied: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_index.py (from rev 12014, CalendarServer/trunk/twext/who/test/test_index.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_index.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_index.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -0,0 +1,486 @@
+##
+# Copyright (c) 2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Indexed directory service base implementation tests.
+"""
+
+from twisted.trial import unittest
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twext.who.idirectory import FieldName as BaseFieldName
+from twext.who.idirectory import QueryNotSupportedError
+from twext.who.expression import MatchExpression, MatchType
+from twext.who.index import DirectoryService, DirectoryRecord
+from twext.who.test import test_directory
+from twext.who.test.test_directory import RecordStorage
+
+
+
+def noLoadDirectoryService(superClass):
+    """
+    Creates an indexed directory service that has a no-op implementation of
+    L{DirectoryService.loadRecords}.
+
+    @param superClass: The superclass of the new service.
+    @type superClass: subclass of L{DirectoryService}
+
+    @return: A new directory service class.
+    @rtype: subclass of C{superClass}
+    """
+    assert issubclass(superClass, DirectoryService)
+
+    class NoLoadDirectoryService(superClass):
+        def loadRecords(self):
+            pass
+
+        def indexedRecordsFromMatchExpression(self, *args, **kwargs):
+            self._calledIndexed = True
+            return superClass.indexedRecordsFromMatchExpression(
+                self, *args, **kwargs
+            )
+
+        def unIndexedRecordsFromMatchExpression(self, *args, **kwargs):
+            self._calledUnindexed = True
+            return superClass.unIndexedRecordsFromMatchExpression(
+                self, *args, **kwargs
+            )
+
+    return NoLoadDirectoryService
+
+
+class BaseDirectoryServiceTest(test_directory.BaseDirectoryServiceTest):
+    """
+    Tests for indexed directory services.
+    """
+
+    def noLoadServicePopulated(self):
+        service = self.service(
+            subClass=noLoadDirectoryService(self.serviceClass)
+        )
+
+        records = RecordStorage(service, DirectoryRecord)
+        service.indexRecords(records)
+        service.records = records
+
+        return service
+
+    def test_indexRecords_positive(self):
+        """
+        L{DirectoryService.indexRecords} ensures all record data is in the
+        index.
+        """
+        service = self.noLoadServicePopulated()
+        index = service.index
+
+        # Verify that the fields that should be indexed are, in fact, indexed
+        # for each record.
+        for record in service.records:
+            for fieldName in service.indexedFields:
+                values = record.fields.get(fieldName, None)
+
+                if values is None:
+                    continue
+
+                if not BaseFieldName.isMultiValue(fieldName):
+                    values = (values,)
+
+                for value in values:
+                    self.assertIn(fieldName, index)
+                    self.assertIn(value, index[fieldName])
+
+                    indexedRecords = index[fieldName][value]
+                    self.assertIn(record, indexedRecords)
+
+
+    def test_indexRecords_negative(self):
+        """
+        L{DirectoryService.indexRecords} does not have extra data in the index.
+        """
+        service = self.noLoadServicePopulated()
+        index = service.index
+
+        # Verify that all data in the index cooresponds to the records passed
+        # in.
+        for fieldName, fieldIndex in index.iteritems():
+            for fieldValue, records in fieldIndex.iteritems():
+                for record in records:
+                    self.assertIn(fieldName, record.fields)
+                    values = record.fields[fieldName]
+
+                    if not BaseFieldName.isMultiValue(fieldName):
+                        values = (values,)
+
+                    self.assertIn(fieldValue, values)
+
+
+    def test_flush(self):
+        """
+        C{flush} empties the index.
+        """
+        service = self.noLoadServicePopulated()
+
+        self.assertFalse(emptyIndex(service.index))  # Test the test
+        service.flush()
+        self.assertTrue(emptyIndex(service.index))
+
+
+    @inlineCallbacks
+    def _test_indexedRecordsFromMatchExpression(
+        self, inOut, matchType, fieldName=BaseFieldName.shortNames,
+    ):
+        service = self.noLoadServicePopulated()
+
+        for subString, uids in (inOut):
+            records = yield service.indexedRecordsFromMatchExpression(
+                MatchExpression(
+                    fieldName, subString,
+                    matchType
+                )
+            )
+            self.assertEquals(
+                set((record.uid for record in records)),
+                set(uids)
+            )
+
+
+    def test_indexedRecordsFromMatchExpression_startsWith(self):
+        """
+        L{DirectoryService.indexedRecordsFromMatchExpression} with a startsWith
+        expression.
+        """
+        return self._test_indexedRecordsFromMatchExpression(
+            (
+                (u"w", (u"__wsanchez__",)),           # Duplicates
+                (u"dr", (u"__dre__", u"__dreid__")),  # Multiple
+                (u"sage", (u"__sagen__",)),           # Single
+            ),
+            MatchType.startsWith
+        )
+
+
+    def test_indexedRecordsFromMatchExpression_contains(self):
+        """
+        L{DirectoryService.indexedRecordsFromMatchExpression} with a contains
+        expression.
+        """
+        return self._test_indexedRecordsFromMatchExpression(
+            (
+                (u"sanch", (u"__wsanchez__",)),       # Duplicates
+                (u"dr", (u"__dre__", u"__dreid__")),  # Multiple
+                (u"agen", (u"__sagen__",)),           # Single
+            ),
+            MatchType.contains
+        )
+
+
+    def test_indexedRecordsFromMatchExpression_equals(self):
+        """
+        L{DirectoryService.indexedRecordsFromMatchExpression} with an equals
+        expression.
+        """
+        return self._test_indexedRecordsFromMatchExpression(
+            (
+                (u"wsanchez", (u"__wsanchez__",)),  # MultiValue
+                (u"dre", (u"__dre__",)),            # Single value
+            ),
+            MatchType.equals
+        )
+
+
+    def test_indexedRecordsFromMatchExpression_notIndexed(self):
+        """
+        L{DirectoryService.indexedRecordsFromMatchExpression} with an
+        unindexed field name.
+        """
+        result = self._test_indexedRecordsFromMatchExpression(
+            (
+                (u"zehcnasw", (u"__wsanchez__",)),
+            ),
+            MatchType.equals,
+            fieldName=BaseFieldName.password
+        )
+        self.assertFailure(result, TypeError)
+
+
+    def test_indexedRecordsFromMatchExpression_notMatchExpression(self):
+        """
+        L{DirectoryService.indexedRecordsFromMatchExpression} with a
+        non-match expression.
+        """
+        result = self._test_indexedRecordsFromMatchExpression(
+            (
+                (u"zehcnasw", (u"__wsanchez__",)),
+            ),
+            "Not a match type we know about"
+        )
+        self.assertFailure(result, NotImplementedError)
+
+
+    @inlineCallbacks
+    def _test_unIndexedRecordsFromMatchExpression(
+        self, inOut, matchType, fieldName=BaseFieldName.fullNames,
+    ):
+        service = self.noLoadServicePopulated()
+
+        for subString, uids in (inOut):
+            records = yield service.unIndexedRecordsFromMatchExpression(
+                MatchExpression(
+                    fieldName, subString,
+                    matchType
+                )
+            )
+            self.assertEquals(
+                set((record.uid for record in records)),
+                set(uids)
+            )
+
+
+    def test_unIndexedRecordsFromMatchExpression_startsWith(self):
+        """
+        L{DirectoryService.unIndexedRecordsFromMatchExpression} with a
+        startsWith expression.
+        """
+        return self._test_unIndexedRecordsFromMatchExpression(
+            (
+                (u"Wilfredo", (u"__wsanchez__",)),    # Duplicates
+                (u"A", (u"__alyssa__", u"__dre__")),  # Multiple
+                (u"Andre", (u"__dre__",)),            # Single
+            ),
+            MatchType.startsWith
+        )
+
+
+    def test_unIndexedRecordsFromMatchExpression_contains(self):
+        """
+        L{DirectoryService.unIndexedRecordsFromMatchExpression} with a contains
+        expression.
+        """
+        return self._test_unIndexedRecordsFromMatchExpression(
+            (
+                (u"Sanchez", (u"__wsanchez__",)),     # Duplicates
+                (u"A", (u"__alyssa__", u"__dre__")),  # Multiple
+                (u"LaBra", (u"__dre__",)),            # Single
+            ),
+            MatchType.contains
+        )
+
+
+    def test_unIndexedRecordsFromMatchExpression_equals(self):
+        """
+        L{DirectoryService.unIndexedRecordsFromMatchExpression} with an equals
+        expression.
+        """
+        return self._test_unIndexedRecordsFromMatchExpression(
+            (
+                (u"Wilfredo Sanchez", (u"__wsanchez__",)),  # MultiValue
+                (u"Andre LaBranche", (u"__dre__",)),        # Single value
+            ),
+            MatchType.equals
+        )
+
+
+    def test_unIndexedRecordsFromMatchExpression_indexed(self):
+        """
+        L{DirectoryService.unIndexedRecordsFromMatchExpression} with an
+        indexed field name.
+        """
+        self._test_unIndexedRecordsFromMatchExpression(
+            (
+                (u"wsanchez", (u"__wsanchez__",)),
+            ),
+            MatchType.equals,
+            fieldName=BaseFieldName.shortNames
+        )
+
+
+    def test_unIndexedRecordsFromMatchExpression_notMatchExpression(self):
+        """
+        L{DirectoryService.unIndexedRecordsFromMatchExpression} with a
+        non-match expression.
+        """
+        result = self._test_unIndexedRecordsFromMatchExpression(
+            (
+                (u"zehcnasw", (u"__wsanchez__",)),
+            ),
+            "Not a match type we know about"
+        )
+        self.assertFailure(result, NotImplementedError)
+
+
+    @inlineCallbacks
+    def _test_recordsFromNonCompoundExpression(self, expression):
+        service = self.noLoadServicePopulated()
+        yield service.recordsFromNonCompoundExpression(expression)
+        returnValue(service)
+
+
+    @inlineCallbacks
+    def test_recordsFromNonCompoundExpression_match_indexed(self):
+        """
+        L{DirectoryService.recordsFromNonCompoundExpression} with a
+        L{MatchExpression} for an indexed field calls
+        L{DirectoryRecord.indexedRecordsFromMatchExpression}.
+        """
+        service = yield self._test_recordsFromNonCompoundExpression(
+            MatchExpression(BaseFieldName.shortNames, u"...")
+        )
+        self.assertTrue(getattr(service, "_calledIndexed", False))
+        self.assertFalse(getattr(service, "_calledUnindexed", False))
+
+
+    @inlineCallbacks
+    def test_recordsFromNonCompoundExpression_match_unindexed(self):
+        """
+        L{DirectoryService.recordsFromNonCompoundExpression} with a
+        L{MatchExpression} for an unindexed field calls
+        L{DirectoryRecord.unIndexedRecordsFromMatchExpression}.
+        """
+        service = yield self._test_recordsFromNonCompoundExpression(
+            MatchExpression(BaseFieldName.password, u"...")
+        )
+        self.assertFalse(getattr(service, "_calledIndexed", False))
+        self.assertTrue(getattr(service, "_calledUnindexed", False))
+
+
+    def test_recordsFromNonCompoundExpression_unknown(self):
+        """
+        L{DirectoryService.recordsFromNonCompoundExpression} with a
+        an unknown expression calls superclass, which will result in a
+        L{QueryNotSupportedError}.
+        """
+        result = self._test_recordsFromNonCompoundExpression(object())
+        self.assertFailure(result, QueryNotSupportedError)
+
+
+
+class DirectoryServiceTest(unittest.TestCase, BaseDirectoryServiceTest):
+    """
+    Tests for L{DirectoryService}.
+    """
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
+
+    def test_init_noIndex(self):
+        """
+        Index starts as C{None}.
+        """
+        service = self.service()
+        self.assertTrue(emptyIndex(service._index))
+
+
+    def test_index_get(self):
+        """
+        Getting the C{index} property calls C{loadRecords}.
+        """
+        class TestService(DirectoryService):
+            loaded = False
+
+            def loadRecords(self):
+                self.loaded = True
+
+        service = TestService(u"")
+        service.index
+        self.assertTrue(service.loaded)
+
+
+    def test_loadRecords(self):
+        """
+        L{DirectoryService.loadRecords} raises C{NotImplementedError}.
+        """
+        service = self.service()
+        self.assertRaises(NotImplementedError, service.loadRecords)
+
+
+    def _noop(self):
+        """
+        Does nothing.
+        """
+
+
+    test_recordWithUID = _noop
+    test_recordWithGUID = _noop
+    test_recordsWithRecordType = _noop
+    test_recordWithShortName = _noop
+    test_recordsWithEmailAddress = _noop
+
+
+
+class BaseDirectoryServiceImmutableTest(
+    test_directory.BaseDirectoryServiceImmutableTest
+):
+    """
+    Tests for immutable indexed directory services.
+    """
+
+
+
+class DirectoryServiceImmutableTest(
+    unittest.TestCase, BaseDirectoryServiceImmutableTest
+):
+    """
+    Tests for immutable L{DirectoryService}.
+    """
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
+
+
+class BaseDirectoryRecordTest(test_directory.BaseDirectoryRecordTest):
+    """
+    Tests for indexed directory records.
+    """
+
+
+
+class DirectoryRecordTest(unittest.TestCase, BaseDirectoryRecordTest):
+    """
+    Tests for L{DirectoryRecord}.
+    """
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
+
+    def _noop(self):
+        """
+        Does nothing.
+        """
+
+
+    test_members_group = _noop
+    test_memberships = _noop
+
+
+
+def emptyIndex(index):
+    """
+    Determine whether an index is empty.
+
+    @param index: An index.
+    @type index: L{dict}
+
+    @return: true if C{index} is empty, otherwise false.
+    """
+    if not index:
+        return True
+
+    for fieldName, fieldIndex in index.iteritems():
+        for fieldValue, records in fieldIndex.iteritems():
+            for record in records:
+                return False
+
+    return True

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_xml.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/test/test_xml.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -31,16 +31,19 @@
 from twext.who.expression import MatchExpression, MatchType, MatchFlags
 from twext.who.xml import ParseError
 from twext.who.xml import DirectoryService, DirectoryRecord
+from twext.who.test import test_index
 
-from twext.who.test import test_directory
 
 
+class BaseTest(object):
+    def service(self, subClass=None, xmlData=None):
+        return xmlService(
+            self.mktemp(),
+            xmlData=xmlData,
+            serviceClass=subClass
+        )
 
-class BaseTest(unittest.TestCase):
-    def service(self, xmlData=None):
-        return xmlService(self.mktemp(), xmlData)
 
-
     def assertRecords(self, records, uids):
         self.assertEquals(
             frozenset((record.uid for record in records)),
@@ -49,18 +52,7 @@
 
 
 
-class DirectoryServiceBaseTest(
-    BaseTest,
-    test_directory.BaseDirectoryServiceTest,
-):
-    def test_repr(self):
-        service = self.service()
-
-        self.assertEquals(repr(service), "<TestService (not loaded)>")
-        service.loadRecords()
-        self.assertEquals(repr(service), "<TestService u'xyzzy'>")
-
-
+class DirectoryServiceConvenienceTestMixIn(BaseTest):
     @inlineCallbacks
     def test_recordWithUID(self):
         service = self.service()
@@ -176,7 +168,24 @@
 
 
 
-class DirectoryServiceRealmTest(BaseTest):
+class DirectoryServiceTest(
+    unittest.TestCase,
+    DirectoryServiceConvenienceTestMixIn,
+    test_index.BaseDirectoryServiceTest,
+):
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
+    def test_repr(self):
+        service = self.service()
+
+        self.assertEquals(repr(service), "<TestService (not loaded)>")
+        service.loadRecords()
+        self.assertEquals(repr(service), "<TestService u'xyzzy'>")
+
+
+
+class DirectoryServiceRealmTest(unittest.TestCase, BaseTest):
     def test_realmNameImmutable(self):
         def setRealmName():
             service = self.service()
@@ -186,7 +195,7 @@
 
 
 
-class DirectoryServiceParsingTest(BaseTest):
+class DirectoryServiceParsingTest(unittest.TestCase, BaseTest):
     def test_reloadInterval(self):
         service = self.service()
 
@@ -232,7 +241,7 @@
         except ParseError as e:
             self.assertTrue(str(e).startswith("Incorrect root element"), e)
         else:
-            raise AssertionError
+            raise AssertionError("Expected ParseError")
 
 
     def test_noRealmName(self):
@@ -250,7 +259,7 @@
         except ParseError as e:
             self.assertTrue(str(e).startswith("No realm name"), e)
         else:
-            raise AssertionError
+            raise AssertionError("Expected ParseError")
 
 
     def test_unknownFieldElementsClean(self):
@@ -301,7 +310,7 @@
 
 
 
-class DirectoryServiceQueryTest(BaseTest):
+class DirectoryServiceQueryTest(unittest.TestCase, BaseTest):
     @inlineCallbacks
     def test_queryAnd(self):
         service = self.service()
@@ -663,7 +672,7 @@
 
 
 
-class DirectoryServiceMutableTest(BaseTest):
+class DirectoryServiceMutableTest(unittest.TestCase, BaseTest):
     @inlineCallbacks
     def test_updateRecord(self):
         service = self.service()
@@ -699,7 +708,7 @@
         newRecord = DirectoryRecord(
             service,
             fields={
-                service.fieldName.uid:        u"__plugh__",
+                service.fieldName.uid: u"__plugh__",
                 service.fieldName.recordType: service.recordType.user,
                 service.fieldName.shortNames: (u"plugh",),
             }
@@ -723,7 +732,7 @@
         newRecord = DirectoryRecord(
             service,
             fields={
-                service.fieldName.uid:        u"__plugh__",
+                service.fieldName.uid: u"__plugh__",
                 service.fieldName.recordType: service.recordType.user,
                 service.fieldName.shortNames: (u"plugh",),
             }
@@ -756,9 +765,16 @@
 
 
 
-class DirectoryRecordTest(BaseTest, test_directory.BaseDirectoryRecordTest):
+class DirectoryRecordTest(
+    unittest.TestCase,
+    BaseTest,
+    test_index.BaseDirectoryRecordTest
+):
+    serviceClass = DirectoryService
+    directoryRecordClass = DirectoryRecord
+
     @inlineCallbacks
-    def test_members(self):
+    def test_members_group(self):
         service = self.service()
 
         record = (yield service.recordWithUID(u"__wsanchez__"))
@@ -790,7 +806,7 @@
         )
 
     @inlineCallbacks
-    def test_groups(self):
+    def test_memberships(self):
         service = self.service()
 
         record = (yield service.recordWithUID(u"__wsanchez__"))
@@ -832,7 +848,13 @@
     filePath = FilePath(tmp)
     filePath.setContent(xmlData)
 
-    return serviceClass(filePath)
+    try:
+        return serviceClass(filePath)
+    except Exception as e:
+        raise AssertionError(
+            "Unable to instantiate XML service {0}: {1}"
+            .format(serviceClass, e)
+        )
 
 
 

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/xml.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twext/who/xml.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -242,7 +242,7 @@
         if not realmName:
             raise ParseError("No realm name.")
 
-        unknownRecordTypes   = set()
+        unknownRecordTypes = set()
         unknownFieldElements = set()
 
         records = set()
@@ -259,32 +259,17 @@
         # Store results
         #
 
-        index = {}
+        self.flush()
+        self.indexRecords(records)
 
-        for fieldName in self.indexedFields:
-            index[fieldName] = {}
-
-        for record in records:
-            for fieldName in self.indexedFields:
-                values = record.fields.get(fieldName, None)
-
-                if values is not None:
-                    if not BaseFieldName.isMultiValue(fieldName):
-                        values = (values,)
-
-                    for value in values:
-                        index[fieldName].setdefault(value, set()).add(record)
-
         self._realmName = realmName
 
-        self._unknownRecordTypes   = unknownRecordTypes
+        self._unknownRecordTypes = unknownRecordTypes
         self._unknownFieldElements = unknownFieldElements
 
         self._cacheTag = cacheTag
         self._lastRefresh = now
 
-        self.index = index
-
         return etree
 
 
@@ -348,11 +333,11 @@
     def flush(self):
         BaseDirectoryService.flush(self)
 
-        self._realmName            = None
-        self._unknownRecordTypes   = None
+        self._realmName = None
+        self._unknownRecordTypes = None
         self._unknownFieldElements = None
-        self._cacheTag             = None
-        self._lastRefresh          = 0
+        self._cacheTag = None
+        self._lastRefresh = 0
 
 
     def updateRecords(self, records, create=False):

Copied: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twisted/plugins/masterchild.py (from rev 12014, CalendarServer/trunk/twisted/plugins/masterchild.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twisted/plugins/masterchild.py	                        (rev 0)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twisted/plugins/masterchild.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -0,0 +1,58 @@
+##
+# Copyright (c) 2010-2013 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+from zope.interface import implementer
+
+from twisted.python.reflect import namedClass
+from twisted.plugin import IPlugin
+from twisted.application.service import IServiceMaker
+
+from twext.application.masterchild import MasterOptions, ChildOptions
+
+
+ at implementer(IPlugin, IServiceMaker)
+class ServiceMaker(object):
+    def __init__(self, name, description, options, serviceMakerClass):
+        self.tapname = name
+        self.description = description
+        self.options = options
+        self.serviceMakerClass = serviceMakerClass
+        self._serviceMaker = None
+
+
+    def makeService(self, options):
+        if self._serviceMaker is None:
+            self._serviceMaker = namedClass(self.serviceMakerClass)()
+
+        return self._serviceMaker.makeService(options)
+
+
+
+masterServiceMaker = ServiceMaker(
+    "master",
+    "Master process application container",
+    MasterOptions,
+    "twext.application.masterchild.MasterServiceMaker"
+)
+
+
+
+childServiceMaker = ServiceMaker(
+    "child",
+    "Child process application container",
+    ChildOptions,
+    "twext.application.masterchild.ChildServiceMaker"
+)

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/ical.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/ical.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -286,8 +286,7 @@
 
     def parameterValue(self, name, default=None):
         """
-        Returns a single value for the given parameter.  Raises
-        InvalidICalendarDataError if the parameter has more than one value.
+        Returns a single value for the given parameter.
         """
         try:
             return self._pycalendar.getParameterValue(name)
@@ -295,6 +294,16 @@
             return default
 
 
+    def parameterValues(self, name, default=None):
+        """
+        Returns a multi-value C{list} for the given parameter.
+        """
+        try:
+            return self._pycalendar.getParameterValues(name)
+        except KeyError:
+            return default
+
+
     def hasParameter(self, paramname):
         return self._pycalendar.hasParameter(paramname)
 

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/stdconfig.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/stdconfig.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -304,7 +304,7 @@
                                     # the master process, rather than having
                                     # each client make its connections directly.
 
-    "FailIfUpgradeNeeded"  : True, # Set to True to prevent the server or utility tools
+    "FailIfUpgradeNeeded"  : True, # Set to True to prevent the server or utility
                                    # tools from running if the database needs a schema
                                    # upgrade.
     "StopAfterUpgradeTriggerFile" : "stop_after_upgrade",   # if this file exists in ConfigRoot, stop
@@ -892,7 +892,8 @@
 
     # Support for Content-Encoding compression options as specified in
     # RFC2616 Section 3.5
-    "ResponseCompression": True,
+    # Defaults off, because it weakens TLS (CRIME attack).
+    "ResponseCompression": False,
 
     # The retry-after value (in seconds) to return with a 503 error
     "HTTPRetryAfter": 180,

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_config.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_config.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_config.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -34,7 +34,7 @@
 <dict>
 
   <key>ResponseCompression</key>
-  <false/>
+  <true/>
 
   <key>HTTPPort</key>
   <integer>8008</integer>
@@ -73,7 +73,7 @@
 
 
 def _testResponseCompression(testCase):
-    testCase.assertEquals(config.ResponseCompression, False)
+    testCase.assertEquals(config.ResponseCompression, True)
 
 
 
@@ -114,19 +114,19 @@
 
 
     def testLoadConfig(self):
-        self.assertEquals(config.ResponseCompression, True)
+        self.assertEquals(config.ResponseCompression, False)
 
         config.load(self.testConfig)
 
-        self.assertEquals(config.ResponseCompression, False)
+        self.assertEquals(config.ResponseCompression, True)
 
 
     def testScoping(self):
-        self.assertEquals(config.ResponseCompression, True)
+        self.assertEquals(config.ResponseCompression, False)
 
         config.load(self.testConfig)
 
-        self.assertEquals(config.ResponseCompression, False)
+        self.assertEquals(config.ResponseCompression, True)
 
         _testResponseCompression(self)
 

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_icalendar.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/test/test_icalendar.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -21,7 +21,7 @@
 from twisted.trial.unittest import SkipTest
 
 from twistedcaldav.ical import Component, Property, InvalidICalendarDataError, \
-    normalizeCUAddress
+    normalizeCUAddress, normalize_iCalStr
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 import twistedcaldav.test.util
 
@@ -1152,6 +1152,82 @@
             self.assertEqual(result, str(component).replace("\r", ""))
 
 
+    def test_parameter_multi_values(self):
+        caldata = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;MEMBER="urn:uuid:group01","urn:uuid:group02";PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+        caldata2 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;MEMBER="urn:uuid:group01","urn:uuid:group02","urn:uuid:group03";PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+        caldata3 = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890-1
+DTSTART:20071114T000000Z
+ATTENDEE:mailto:user01 at example.com
+ATTENDEE;MEMBER="urn:uuid:group01";PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER:mailto:user01 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
+
+        component = Component.fromString(caldata)
+        attendee = component.masterComponent().getAttendeeProperty(["mailto:user02 at example.com", ])
+        self.assertTrue(attendee is not None)
+
+        # Single value retrieved as multi-value
+        partstat = attendee.parameterValues("PARTSTAT")
+        self.assertEqual(partstat, ["NEEDS-ACTION"])
+
+        # Multi-value retrieved as single-value
+        member = attendee.parameterValue("MEMBER")
+        self.assertEqual(member, "urn:uuid:group01")
+
+        # Multi-value retrieved as multi-value
+        members = attendee.parameterValues("MEMBER")
+        self.assertEqual(members, ["urn:uuid:group01", "urn:uuid:group02"])
+
+        # Multi-value add a new value
+        members = attendee.parameterValues("MEMBER")
+        members.append("urn:uuid:group03")
+        attendee.setParameter("MEMBER", members)
+        members = attendee.parameterValues("MEMBER")
+        self.assertEqual(members, ["urn:uuid:group01", "urn:uuid:group02", "urn:uuid:group03"])
+        self.assertEqual(normalize_iCalStr(str(component)), normalize_iCalStr(caldata2))
+
+        # Multi-value back to one
+        members = attendee.parameterValues("MEMBER")
+        del members[1:]
+        attendee.setParameter("MEMBER", members)
+        members = attendee.parameterValues("MEMBER")
+        self.assertEqual(members, ["urn:uuid:group01"])
+        self.assertEqual(normalize_iCalStr(str(component)), normalize_iCalStr(caldata3))
+
+
     def test_add_property_with_valuetype(self):
         data = """BEGIN:VCALENDAR
 VERSION:2.0

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Casablanca.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Casablanca.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Casablanca.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -54,6 +54,7 @@
 RDATE:20100808T000000
 RDATE:20110731T000000
 RDATE:20120720T030000
+RDATE:20120930T030000
 RDATE:20130707T030000
 RDATE:20140629T030000
 RDATE:20150618T030000
@@ -61,6 +62,9 @@
 RDATE:20170527T030000
 RDATE:20180516T030000
 RDATE:20190506T030000
+RDATE:20200424T030000
+RDATE:20210413T030000
+RDATE:20220403T030000
 TZNAME:WET
 TZOFFSETFROM:+0100
 TZOFFSETTO:+0000
@@ -88,17 +92,24 @@
 END:STANDARD
 BEGIN:DAYLIGHT
 DTSTART:20120429T020000
-RRULE:FREQ=YEARLY;UNTIL=20190428T020000Z;BYDAY=-1SU;BYMONTH=4
+RRULE:FREQ=YEARLY;UNTIL=20130428T020000Z;BYDAY=-1SU;BYMONTH=4
 TZNAME:WEST
 TZOFFSETFROM:+0000
 TZOFFSETTO:+0100
 END:DAYLIGHT
 BEGIN:STANDARD
-DTSTART:20120930T030000
-RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=9
+DTSTART:20131027T030000
+RRULE:FREQ=YEARLY;UNTIL=20221030T020000Z;BYDAY=-1SU;BYMONTH=10
 TZNAME:WET
 TZOFFSETFROM:+0100
 TZOFFSETTO:+0000
 END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20140330T020000
+RRULE:FREQ=YEARLY;UNTIL=20220327T020000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:WEST
+TZOFFSETFROM:+0000
+TZOFFSETTO:+0100
+END:DAYLIGHT
 END:VTIMEZONE
 END:VCALENDAR

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/El_Aaiun.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -15,9 +15,84 @@
 BEGIN:STANDARD
 DTSTART:19760414T000000
 RDATE:19760414T000000
-TZNAME:WET
+TZNAME:WEST
 TZOFFSETFROM:-0100
 TZOFFSETTO:+0000
 END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19760501T000000
+RRULE:FREQ=YEARLY;UNTIL=19770501T000000Z;BYMONTH=5
+TZNAME:WEST
+TZOFFSETFROM:+0000
+TZOFFSETTO:+0100
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:19760801T000000
+RDATE:19760801T000000
+RDATE:19770928T000000
+RDATE:19780804T000000
+RDATE:20080901T000000
+RDATE:20090821T000000
+RDATE:20100808T000000
+RDATE:20110731T000000
+RDATE:20120720T030000
+RDATE:20120930T030000
+RDATE:20130707T030000
+RDATE:20140629T030000
+RDATE:20150618T030000
+RDATE:20160607T030000
+RDATE:20170527T030000
+RDATE:20180516T030000
+RDATE:20190506T030000
+RDATE:20200424T030000
+RDATE:20210413T030000
+RDATE:20220403T030000
+TZNAME:WET
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0000
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:19780601T000000
+RDATE:19780601T000000
+RDATE:20080601T000000
+RDATE:20090601T000000
+RDATE:20100502T000000
+RDATE:20110403T000000
+RDATE:20120820T020000
+RDATE:20130810T020000
+RDATE:20140729T020000
+RDATE:20150718T020000
+RDATE:20160707T020000
+RDATE:20170626T020000
+RDATE:20180615T020000
+RDATE:20190605T020000
+RDATE:20200524T020000
+RDATE:20210513T020000
+RDATE:20220503T020000
+TZNAME:WEST
+TZOFFSETFROM:+0000
+TZOFFSETTO:+0100
+END:DAYLIGHT
+BEGIN:DAYLIGHT
+DTSTART:20120429T020000
+RRULE:FREQ=YEARLY;UNTIL=20130428T020000Z;BYDAY=-1SU;BYMONTH=4
+TZNAME:WEST
+TZOFFSETFROM:+0000
+TZOFFSETTO:+0100
+END:DAYLIGHT
+BEGIN:STANDARD
+DTSTART:20131027T030000
+RRULE:FREQ=YEARLY;UNTIL=20221030T020000Z;BYDAY=-1SU;BYMONTH=10
+TZNAME:WET
+TZOFFSETFROM:+0100
+TZOFFSETTO:+0000
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20140330T020000
+RRULE:FREQ=YEARLY;UNTIL=20220327T020000Z;BYDAY=-1SU;BYMONTH=3
+TZNAME:WEST
+TZOFFSETFROM:+0000
+TZOFFSETTO:+0100
+END:DAYLIGHT
 END:VTIMEZONE
 END:VCALENDAR

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Tripoli.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Tripoli.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Africa/Tripoli.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -20,6 +20,7 @@
 RDATE:19850406T000000
 RDATE:19860404T000000
 RDATE:19970404T000000
+RDATE:20130329T010000
 TZNAME:CEST
 TZOFFSETFROM:+0100
 TZOFFSETTO:+0200
@@ -82,23 +83,10 @@
 BEGIN:STANDARD
 DTSTART:19971004T000000
 RDATE:19971004T000000
+RDATE:20131025T020000
 TZNAME:EET
 TZOFFSETFROM:+0200
 TZOFFSETTO:+0200
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20130329T010000
-RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=3
-TZNAME:CEST
-TZOFFSETFROM:+0100
-TZOFFSETTO:+0200
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20131025T020000
-RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=10
-TZNAME:CET
-TZOFFSETFROM:+0200
-TZOFFSETTO:+0100
-END:STANDARD
 END:VTIMEZONE
 END:VCALENDAR

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Eirunepe.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Eirunepe.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Eirunepe.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -51,6 +51,7 @@
 RDATE:19870214T000000
 RDATE:19880207T000000
 RDATE:19940220T000000
+RDATE:20131110T000000
 TZNAME:ACT
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Porto_Acre.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Porto_Acre.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Porto_Acre.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -49,6 +49,7 @@
 RDATE:19860315T000000
 RDATE:19870214T000000
 RDATE:19880207T000000
+RDATE:20131110T000000
 TZNAME:ACT
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Rio_Branco.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Rio_Branco.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/America/Rio_Branco.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -49,6 +49,7 @@
 RDATE:19860315T000000
 RDATE:19870214T000000
 RDATE:19880207T000000
+RDATE:20131110T000000
 TZNAME:ACT
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Brazil/Acre.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Brazil/Acre.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Brazil/Acre.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -49,6 +49,7 @@
 RDATE:19860315T000000
 RDATE:19870214T000000
 RDATE:19880207T000000
+RDATE:20131110T000000
 TZNAME:ACT
 TZOFFSETFROM:-0400
 TZOFFSETTO:-0500

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Libya.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Libya.ics	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/Libya.ics	2013-12-02 19:04:21 UTC (rev 12016)
@@ -20,6 +20,7 @@
 RDATE:19850406T000000
 RDATE:19860404T000000
 RDATE:19970404T000000
+RDATE:20130329T010000
 TZNAME:CEST
 TZOFFSETFROM:+0100
 TZOFFSETTO:+0200
@@ -82,23 +83,10 @@
 BEGIN:STANDARD
 DTSTART:19971004T000000
 RDATE:19971004T000000
+RDATE:20131025T020000
 TZNAME:EET
 TZOFFSETFROM:+0200
 TZOFFSETTO:+0200
 END:STANDARD
-BEGIN:DAYLIGHT
-DTSTART:20130329T010000
-RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=3
-TZNAME:CEST
-TZOFFSETFROM:+0100
-TZOFFSETTO:+0200
-END:DAYLIGHT
-BEGIN:STANDARD
-DTSTART:20131025T020000
-RRULE:FREQ=YEARLY;BYDAY=-1FR;BYMONTH=10
-TZNAME:CET
-TZOFFSETFROM:+0200
-TZOFFSETTO:+0100
-END:STANDARD
 END:VTIMEZONE
 END:VCALENDAR

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/timezones.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/timezones.xml	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/timezones.xml	2013-12-02 19:04:21 UTC (rev 12016)
@@ -2,7 +2,7 @@
 <!DOCTYPE timezones SYSTEM "timezones.dtd">
 
 <timezones>
-  <dtstamp>2013-10-01T01:19:11Z</dtstamp>
+  <dtstamp>2013-11-15T16:10:31Z</dtstamp>
   <timezone>
     <tzid>Africa/Abidjan</tzid>
     <dtstamp>2011-10-05T11:50:21Z</dtstamp>
@@ -78,8 +78,8 @@
   </timezone>
   <timezone>
     <tzid>Africa/Casablanca</tzid>
-    <dtstamp>2013-07-11T02:11:45Z</dtstamp>
-    <md5>b4e345b053c4699911078dcd16854bab</md5>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
+    <md5>4f58dbcb4f7e6dfa7af3d710aeff97b4</md5>
   </timezone>
   <timezone>
     <tzid>Africa/Ceuta</tzid>
@@ -113,8 +113,8 @@
   </timezone>
   <timezone>
     <tzid>Africa/El_Aaiun</tzid>
-    <dtstamp>2011-10-05T11:50:21Z</dtstamp>
-    <md5>494500808e8542fd83e4706654c43545</md5>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
+    <md5>7933d45461e9f988ebfc9d062ce894e4</md5>
   </timezone>
   <timezone>
     <tzid>Africa/Freetown</tzid>
@@ -264,9 +264,9 @@
   </timezone>
   <timezone>
     <tzid>Africa/Tripoli</tzid>
-    <dtstamp>2013-01-14T15:32:16Z</dtstamp>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
     <alias>Libya</alias>
-    <md5>f59e5f16eec995c112b8b27580922fdd</md5>
+    <md5>6e8040bfd898654905bfd0a49c64e365</md5>
   </timezone>
   <timezone>
     <tzid>Africa/Tunis</tzid>
@@ -570,8 +570,8 @@
   </timezone>
   <timezone>
     <tzid>America/Eirunepe</tzid>
-    <dtstamp>2011-10-05T11:50:21Z</dtstamp>
-    <md5>d3c4df84162ebac445e1c2dcdb81f3b2</md5>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
+    <md5>cc34b66260adda629311a54df088470b</md5>
   </timezone>
   <timezone>
     <tzid>America/El_Salvador</tzid>
@@ -964,8 +964,8 @@
   </timezone>
   <timezone>
     <tzid>America/Porto_Acre</tzid>
-    <dtstamp>2011-10-05T11:50:21Z</dtstamp>
-    <md5>ce88b8461b7217e1d9ec8f4dfac34670</md5>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
+    <md5>b2b04e3c9c1dab12a3764b99dd8536fc</md5>
   </timezone>
   <timezone>
     <tzid>America/Porto_Velho</tzid>
@@ -1006,10 +1006,10 @@
   </timezone>
   <timezone>
     <tzid>America/Rio_Branco</tzid>
-    <dtstamp>2011-10-05T11:50:21Z</dtstamp>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
     <alias>America/Porto_Acre</alias>
     <alias>Brazil/Acre</alias>
-    <md5>c41ff8b67906037ce014d42019e5831f</md5>
+    <md5>51273c4521cd00a8ecdcff336f0c9503</md5>
   </timezone>
   <timezone>
     <tzid>America/Rosario</tzid>
@@ -1885,8 +1885,8 @@
   </timezone>
   <timezone>
     <tzid>Brazil/Acre</tzid>
-    <dtstamp>2011-10-05T11:50:21Z</dtstamp>
-    <md5>b034140cdfc442b0f989f6a6dd6ec620</md5>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
+    <md5>ad33124888f5c5dd9bed9317ef0bb953</md5>
   </timezone>
   <timezone>
     <tzid>Brazil/DeNoronha</tzid>
@@ -2641,8 +2641,8 @@
   </timezone>
   <timezone>
     <tzid>Libya</tzid>
-    <dtstamp>2013-01-14T15:32:16Z</dtstamp>
-    <md5>f514b497bd861c14aa21612f9101c50c</md5>
+    <dtstamp>2013-11-15T16:10:31Z</dtstamp>
+    <md5>e6ac54ea0ab33dd6fd125a71fc34cf46</md5>
   </timezone>
   <timezone>
     <tzid>MET</tzid>

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/version.txt
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/version.txt	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/twistedcaldav/zoneinfo/version.txt	2013-12-02 19:04:21 UTC (rev 12016)
@@ -1 +1 @@
-IANA Timezone Registry: 2013f
\ No newline at end of file
+IANA Timezone Registry: 2013h
\ No newline at end of file

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/base.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/base.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/base.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -214,7 +214,13 @@
 
     def __delitem__(self, key):
         # Handle per-user behavior
-        if self.isGlobalProperty(key):
+        if self.isShadowableProperty(key):
+            try:
+                self._delitem_uid(key, self._perUser)
+            except KeyError:
+                # It is OK for shadowable delete to fail
+                pass
+        elif self.isGlobalProperty(key):
             self._delitem_uid(key, self._defaultUser)
         else:
             self._delitem_uid(key, self._perUser)

Modified: CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/test/base.py
===================================================================
--- CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/test/base.py	2013-12-02 18:15:36 UTC (rev 12015)
+++ CalendarServer/branches/users/cdaboo/sharing-in-the-store/txdav/base/propertystore/test/base.py	2013-12-02 19:04:21 UTC (rev 12016)
@@ -206,6 +206,41 @@
 
 
     @inlineCallbacks
+    def test_peruserShadow_delete(self):
+        """
+        Delete a shadowable property that has not been overridden by the sharee.
+        """
+
+        name = propertyName("shadow")
+
+        self.propertyStore1.setSpecialProperties((name,), ())
+        self.propertyStore2.setSpecialProperties((name,), ())
+
+        value1 = propertyValue("Hello, World1!")
+
+        self.propertyStore1[name] = value1
+        yield self._changed(self.propertyStore1)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+
+        del self.propertyStore2[name]
+        yield self._changed(self.propertyStore2)
+        self.assertEquals(self.propertyStore1.get(name, None), value1)
+        self.assertEquals(self.propertyStore2.get(name, None), value1)
+        self.failUnless(name in self.propertyStore1)
+        self.failUnless(name in self.propertyStore2)
+
+        del self.propertyStore1[name]
+        yield self._changed(self.propertyStore1)
+        self.assertEquals(self.propertyStore1.get(name, None), None)
+        self.assertEquals(self.propertyStore2.get(name, None), None)
+        self.failIf(name in self.propertyStore1)
+        self.failIf(name in self.propertyStore2)
+
+
+    @inlineCallbacks
     def test_peruser_global(self):
 
         name = propertyName("global")
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/0267340e/attachment.html>


More information about the calendarserver-changes mailing list