[CalendarServer-changes] [9107] CalendarServer/branches/users/gaya/ldapdirectorybacker

source_changes at macosforge.org source_changes at macosforge.org
Fri Apr 13 13:13:24 PDT 2012


Revision: 9107
          http://trac.macosforge.org/projects/calendarserver/changeset/9107
Author:   gaya at apple.com
Date:     2012-04-13 13:13:22 -0700 (Fri, 13 Apr 2012)
Log Message:
-----------
update to trunk

Modified Paths:
--------------
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/applepush.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/test/test_applepush.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/util.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/caldav.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/util.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/calverify.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/notifications.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/cmd.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/terminal.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_cmd.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/vfs.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/tables.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/deprovision/caldavd.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/gateway/caldavd.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/principals/caldavd.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-ldaptest.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-odtest.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-xmltest.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/resources/caldavd-resources.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/certupdate/calendarcertupdate.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.dist.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/ical.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/logger.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/population.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/profiles.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/sim.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_ical.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_population.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_profiles.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_sim.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_trafficlogger.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/tools/request_monitor.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/doc/Admin/LoadSimulation.txt
    CalendarServer/branches/users/gaya/ldapdirectorybacker/support/build.sh
    CalendarServer/branches/users/gaya/ldapdirectorybacker/support/submit
    CalendarServer/branches/users/gaya/ldapdirectorybacker/test
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/internet/sendfdport.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/python/sendmsg.c
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/http.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/method/put_common.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/resource.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/stream.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_metafd.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_stream.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/customxml.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/dateops.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/resources/caldavd.plist
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/extensions.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/ical.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/method/put_common.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/scheduling/scheduler.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/sharing.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/storebridge.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_icalendar.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/base/datastore/util.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/sql.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/test/test_sql.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/util.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql_legacy.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/test/util.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/xml/element.py

Added Paths:
-----------
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/platform/darwin/od/setup_testusers.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/directory.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_vfs.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/test_calverify.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_webadmin.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/webadmin.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_dateops.py

Removed Paths:
-------------
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/test/test_caldav.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml
    CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report_href.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_post_availability.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principals_report.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/stream.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/test/test_stream.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/sudo.py
    CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/test_sudo.py

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/platform/darwin/od/setup_testusers.py (from rev 9105, CalendarServer/trunk/calendarserver/platform/darwin/od/setup_testusers.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/platform/darwin/od/setup_testusers.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/platform/darwin/od/setup_testusers.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,144 @@
+##
+# Copyright (c) 2012 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 os
+import sys
+import odframework
+import dsattributes
+from getopt import getopt, GetoptError
+
+
+def usage(e=None):
+    name = os.path.basename(sys.argv[0])
+    print "usage: %s [options] local_user local_password" % (name,)
+    print ""
+    print " Configures local directory for test users"
+    print ""
+    print "options:"
+    print " -h --help: print this help and exit"
+    if e:
+        sys.exit(1)
+    else:
+        sys.exit(0)
+
+
+def lookupRecordName(node, recordType, name):
+    query, error = odframework.ODQuery.queryWithNode_forRecordTypes_attribute_matchType_queryValues_returnAttributes_maximumResults_error_(
+        node,
+        recordType,
+        dsattributes.kDSNAttrRecordName,
+        dsattributes.eDSExact,
+        name,
+        None,
+        0,
+        None)
+    if error:
+        raise ODError(error)
+    records, error = query.resultsAllowingPartial_error_(False, None)
+    if error:
+        raise ODError(error)
+
+    if len(records) < 1:
+        return None
+    if len(records) > 1:
+        raise ODError("Multiple records for '%s' were found" % (name,))
+
+    return records[0]
+
+def createRecord(node, recordType, recordName, attrs):
+    record, error = node.createRecordWithRecordType_name_attributes_error_(
+        recordType,
+        recordName,
+        attrs,
+        None)
+    if error:
+        print error
+        raise ODError(error)
+    return record
+
+def main():
+
+    try:
+        (optargs, args) = getopt(sys.argv[1:], "h", ["help"])
+    except GetoptError, e:
+        usage(e)
+
+    for opt, arg in optargs:
+        if opt in ("-h", "--help"):
+            usage()
+
+    if len(args) != 2:
+        usage()
+
+    localUser, localPassword = args
+
+    session = odframework.ODSession.defaultSession()
+
+    nodeName = "/Local/Default"
+    node, error = odframework.ODNode.nodeWithSession_name_error_(session, nodeName, None)
+    if error:
+        print error
+        raise ODError(error)
+
+    result, error = node.setCredentialsWithRecordType_recordName_password_error_(
+        dsattributes.kDSStdRecordTypeUsers,
+        localUser,
+        localPassword,
+        None
+    )
+    if error:
+        print "Unable to authenticate with directory %s: %s" % (nodeName, error)
+        raise ODError(error)
+
+    print "Successfully authenticated with directory %s" % (nodeName,)
+
+    print "Creating users within %s:" % (nodeName,)
+    for i in xrange(99):
+        j = i+1
+        recordName = "user%02d" % (j,)
+        password = "user%02d" % (j,)
+        attrs = {
+            dsattributes.kDS1AttrFirstName : ["User"],
+            dsattributes.kDS1AttrLastName  : ["%02d" % (j,)],
+            dsattributes.kDS1AttrDistinguishedName : ["User %02d" % (j,)],
+            dsattributes.kDSNAttrEMailAddress : ["user%02d at example.com" % (j,)],
+            dsattributes.kDS1AttrGeneratedUID : ["user%02d" % (j,)],
+        }
+
+        record = lookupRecordName(node, dsattributes.kDSStdRecordTypeUsers, recordName)
+        if record is None:
+            print "Creating user %s" % (recordName,)
+            try:
+                record = createRecord(node, dsattributes.kDSStdRecordTypeUsers, recordName, attrs)
+                print "Successfully created user %s" % (recordName,)
+                result, error = record.changePassword_toPassword_error_(
+                    None, password, None)
+                if error or not result:
+                    print "Failed to set password for %s: %s" % (recordName, error)
+                else:
+                    print "Successfully set password for %s" % (recordName,)
+            except ODError, e:
+                print "Failed to create user %s: %s" % (recordName, e)
+        else:
+            print "User %s already exists" % (recordName,)
+
+
+class ODError(Exception):
+    def __init__(self, error):
+        self.message = (str(error), error.code())
+
+if __name__ == "__main__":
+    main()

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/applepush.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/applepush.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -34,7 +34,7 @@
 import struct
 import time
 from txdav.common.icommondatastore import InvalidSubscriptionValues
-from calendarserver.push.util import validToken, TokenHistory
+from calendarserver.push.util import validToken, TokenHistory, PushScheduler
 
 
 
@@ -103,6 +103,8 @@
                     settings[protocol]["PrivateKeyPath"],
                     chainPath=settings[protocol]["AuthorityChainPath"],
                     passphrase=settings[protocol]["Passphrase"],
+                    staggerNotifications=settings["EnableStaggering"],
+                    staggerSeconds=settings["StaggerSeconds"],
                     testConnector=providerTestConnector,
                     reactor=reactor,
                 )
@@ -171,9 +173,12 @@
             if numSubscriptions > 0:
                 self.log_debug("Sending %d APNS notifications for %s" %
                     (numSubscriptions, key))
+                tokens = []
                 for token, uid in subscriptions:
                     if token and uid:
-                        provider.sendNotification(token, key)
+                        tokens.append(token)
+                if tokens:
+                    provider.scheduleNotifications(tokens, key)
 
 
 
@@ -395,8 +400,9 @@
 class APNProviderService(APNConnectionService):
 
     def __init__(self, store, host, port, certPath, keyPath, chainPath="",
-        passphrase="", sslMethod="TLSv1_METHOD", testConnector=None,
-        reactor=None):
+        passphrase="", sslMethod="TLSv1_METHOD",
+        staggerNotifications=False, staggerSeconds=3,
+        testConnector=None, reactor=None):
 
         APNConnectionService.__init__(self, host, port, certPath, keyPath,
             chainPath=chainPath, passphrase=passphrase, sslMethod=sslMethod,
@@ -405,6 +411,11 @@
         self.store = store
         self.factory = None
         self.queue = []
+        if staggerNotifications:
+            self.scheduler = PushScheduler(self.reactor, self.sendNotification,
+                staggerSeconds=staggerSeconds)
+        else:
+            self.scheduler = None
 
     def startService(self):
         self.log_info("APNProviderService startService")
@@ -415,6 +426,8 @@
         self.log_info("APNProviderService stopService")
         if self.factory is not None:
             self.factory.stopTrying()
+        if self.scheduler is not None:
+            self.scheduler.stop()
 
     def clientConnectionMade(self):
         # Service the queue
@@ -427,21 +440,74 @@
                 if token and key:
                     self.sendNotification(token, key)
 
+
+    def scheduleNotifications(self, tokens, key):
+        """
+        The starting point for getting notifications to the APNS server.  If there is
+        a connection to the APNS server, these notifications are scheduled (or directly
+        sent if there is no scheduler).  If there is no connection, the notifications
+        are saved for later.
+
+        @param tokens: The device tokens to schedule notifications for
+        @type tokens: List of strings
+        @param key: The key to use for this batch of notifications
+        @type key: String
+        """
+        # Service has reference to factory has reference to protocol instance
+        connection = getattr(self.factory, "connection", None)
+        if connection is not None:
+            if self.scheduler is not None:
+                self.scheduler.schedule(tokens, key)
+            else:
+                for token in tokens:
+                    self.sendNotification(token, key)
+        else:
+            self._saveForWhenConnected(tokens, key)
+
+
+    def _saveForWhenConnected(self, tokens, key):
+        """
+        Called in order to save notifications that can't be sent now because there
+        is no connection to the APNS server.  (token, key) tuples are appended to
+        the queue which is serviced during clientConnectionMade()
+
+        @param tokens: The device tokens to schedule notifications for
+        @type tokens: List of strings
+        @param key: The key to use for this batch of notifications
+        @type key: String
+        """
+        for token in tokens:
+            tokenKeyPair = (token, key)
+            if tokenKeyPair not in self.queue:
+                self.log_debug("APNProviderService has no connection; queuing: %s %s" % (token, key))
+                self.queue.append((token, key))
+            else:
+                self.log_debug("APNProviderService has no connection; skipping duplicate: %s %s" % (token, key))
+
+
+
     def sendNotification(self, token, key):
+        """
+        If there is a connection the notification is sent right away, otherwise
+        the notification is saved for later.
+
+        @param token: The device token to send a notifications to
+        @type token: Strings
+        @param key: The key to use for this notification
+        @type key: String
+        """
         if not (token and key):
             return
 
         # Service has reference to factory has reference to protocol instance
         connection = getattr(self.factory, "connection", None)
         if connection is None:
-            self.log_debug("APNProviderService has no connection; queuing: %s %s" % (token, key))
-            tokenKeyPair = (token, key)
-            if tokenKeyPair not in self.queue:
-                self.queue.append(tokenKeyPair)
+            self._saveForWhenConnected([token], key)
         else:
             connection.sendNotification(token, key)
 
 
+
 class APNFeedbackProtocol(protocol.Protocol, LoggingMixIn):
     """
     Implements the Feedback portion of APNS

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/test/test_applepush.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/test/test_applepush.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/test/test_applepush.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -45,6 +45,8 @@
             "FeedbackHost" : "feedback.push.apple.com",
             "FeedbackPort" : 2196,
             "FeedbackUpdateSeconds" : 300,
+            "EnableStaggering" : True,
+            "StaggerSeconds" : 3,
             "CalDAV" : {
                 "CertificatePath" : "caldav.cer",
                 "PrivateKeyPath" : "caldav.pem",
@@ -75,11 +77,13 @@
         except InvalidSubscriptionValues:
             pass
 
-        token = "2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"
+        token  = "2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"
+        token2 = "3d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df"
         key1 = "/CalDAV/calendars.example.com/user01/calendar/"
         timestamp1 = 1000
         uid = "D2256BCC-48E2-42D1-BD89-CBA1E4CCDFFB"
         yield txn.addAPNSubscription(token, key1, timestamp1, uid)
+        yield txn.addAPNSubscription(token2, key1, timestamp1, uid)
 
         key2 = "/CalDAV/calendars.example.com/user02/calendar/"
         timestamp2 = 3000
@@ -88,6 +92,7 @@
         subscriptions = (yield txn.apnSubscriptionsBySubscriber(uid))
         self.assertTrue([token, key1, timestamp1] in subscriptions)
         self.assertTrue([token, key2, timestamp2] in subscriptions)
+        self.assertTrue([token2, key1, timestamp1] in subscriptions)
 
         # Verify an update to a subscription with a different uid takes on
         # the new uid
@@ -117,8 +122,9 @@
         # Notification arrives from calendar server
         yield service.enqueue("update", "CalDAV|user01/calendar")
 
-        # The notification should be in the queue
-        self.assertEquals(service.providers["CalDAV"].queue, [(token, key1)])
+        # The notifications should be in the queue
+        self.assertTrue((token, key1) in service.providers["CalDAV"].queue)
+        self.assertTrue((token2, key1) in service.providers["CalDAV"].queue)
 
         # Start the service, making the connection which should service the
         # queue
@@ -139,10 +145,26 @@
             rawData[45:])
         self.assertEquals(payload[0], '{"key" : "%s"}' % (key1,))
         # Verify token history is updated
-        self.assertEquals(providerConnector.service.protocol.history.history,
-            [(1, token)]
-        )
+        self.assertTrue(token in [t for (i, t) in providerConnector.service.protocol.history.history])
+        self.assertTrue(token2 in [t for (i, t) in providerConnector.service.protocol.history.history])
 
+
+        #
+        # Verify staggering behavior
+        #
+
+        # Reset sent data
+        providerConnector.transport.data = None
+        # Send notification while service is connected
+        yield service.enqueue("update", "CalDAV|user01/calendar")
+        clock.advance(1) # so that first push is sent
+        self.assertEquals(len(providerConnector.transport.data), 103)
+        # Reset sent data
+        providerConnector.transport.data = None
+        clock.advance(3) # so that second push is sent
+        self.assertEquals(len(providerConnector.transport.data), 103)
+
+
         def errorTestFunction(status, identifier):
             history.append((status, identifier))
             return succeed(None)
@@ -223,12 +245,18 @@
         )
 
         # Verify processError removes associated subscriptions and history
-        yield providerConnector.service.protocol.processError(8, 1)
+        # First find the id corresponding to token2
+        for (id, t) in providerConnector.service.protocol.history.history:
+            if t == token2:
+                break
+
+        yield providerConnector.service.protocol.processError(8, id)
         # The token for this identifier is gone
-        self.assertEquals(providerConnector.service.protocol.history.history, [])
+        self.assertTrue((id, token2) not in providerConnector.service.protocol.history.history)
+
         # All subscriptions for this token should now be gone
         txn = self.store.newTransaction()
-        subscriptions = (yield txn.apnSubscriptionsByToken(token))
+        subscriptions = (yield txn.apnSubscriptionsByToken(token2))
         yield txn.commit()
         self.assertEquals(subscriptions, [])
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/util.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/util.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/push/util.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -15,6 +15,7 @@
 ##
 
 from OpenSSL import crypto
+from twext.python.log import LoggingMixIn
 
 def getAPNTopicFromCertificate(certPath):
     """
@@ -99,3 +100,66 @@
                 del self.history[index]
                 return token
         return None
+
+
+
+class PushScheduler(LoggingMixIn):
+    """
+    Allows staggered scheduling of push notifications
+    """
+
+    def __init__(self, reactor, callback, staggerSeconds=1):
+        """
+        @param callback: The method to call when it's time to send a push
+        @type callback: callable
+        @param staggerSeconds: The number of seconds to stagger between each
+            push
+        @type staggerSeconds: integer
+        """
+        self.outstanding = {}
+        self.reactor = reactor
+        self.callback = callback
+        self.staggerSeconds = staggerSeconds
+
+    def schedule(self, tokens, key):
+        """
+        Schedules a batch of notifications for the given tokens, staggered
+        with self.staggerSeconds between each one.  Duplicates are ignored,
+        so if a token/key pair is already scheduled and not yet sent, a new
+        one will not be scheduled for that pair.
+
+        @param tokens: The device tokens to schedule notifications for
+        @type tokens: List of strings
+        @param key: The key to use for this batch of notifications
+        @type key: String
+        """
+        scheduleTime = 0.0
+        for token in tokens:
+            internalKey = (token, key)
+            if self.outstanding.has_key(internalKey):
+                self.log_debug("PushScheduler already has this scheduled: %s" %
+                    (internalKey,))
+            else:
+                self.outstanding[internalKey] = self.reactor.callLater(
+                    scheduleTime, self.send, token, key)
+                self.log_debug("PushScheduler scheduled: %s in %.0f sec" %
+                    (internalKey, scheduleTime))
+                scheduleTime += self.staggerSeconds
+
+    def send(self, token, key):
+        """
+        This method is what actually gets scheduled.  Its job is to remove
+        its corresponding entry from the outstanding dict and call the
+        callback.
+        """
+        self.log_debug("PushScheduler fired for %s %s" % (token, key))
+        del self.outstanding[(token, key)]
+        self.callback(token, key)
+
+    def stop(self):
+        """
+        Cancel all outstanding delayed calls
+        """
+        for (token, key), delayed in self.outstanding.iteritems():
+            self.log_debug("PushScheduler cancelling %s %s" % (token, key))
+            delayed.cancel()

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/caldav.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/caldav.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -124,6 +124,36 @@
 
 
 
+def conflictBetweenIPv4AndIPv6():
+    """
+    Is there a conflict between binding an IPv6 and an IPv4 port?  Return True
+    if there is, False if there isn't.
+
+    This is a temporary workaround until maybe Twisted starts setting
+    C{IPPROTO_IPV6 / IPV6_V6ONLY} on IPv6 sockets.
+
+    @return: C{True} if listening on IPv4 conflicts with listening on IPv6.
+    """
+    s4 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s6 = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
+    try:
+        s4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        s6.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        s4.bind(("", 0))
+        s4.listen(1)
+        usedport = s4.getsockname()[1]
+        try:
+            s6.bind(("::", usedport))
+        except socket.error:
+            return True
+        else:
+            return False
+    finally:
+        s4.close()
+        s6.close()
+
+
+
 def _computeEnvVars(parent):
     """
     Compute environment variables to be propagated to child processes.
@@ -650,6 +680,29 @@
         result = self.requestProcessingService(options, store)
         if pool is not None:
             pool.setServiceParent(result)
+
+        # Optionally enable Manhole access
+        if config.Manhole.Enabled:
+            try:
+                from twisted.conch.manhole_tap import makeService as manholeMakeService
+                portString = "tcp:%d:interface=127.0.0.1" % (config.Manhole.StartingPortNumber + int(config.LogID) + 1,)
+                manholeService = manholeMakeService({
+                    "sshPort" : None,
+                    "telnetPort" : portString,
+                    "namespace" : {
+                        "config" : config,
+                        "service" : result,
+                        "store" : store,
+                        "directory" : result.rootResource.getDirectory(),
+                        },
+                    "passwd" : config.Manhole.PasswordFilePath,
+                })
+                manholeService.setServiceParent(result)
+                # Using print because logging isn't ready at this point
+                print "Manhole access enabled: %s" % (portString,)
+            except ImportError:
+                print "Manhole access could not enabled because manhole_tap could not be imported"
+
         return result
 
 
@@ -702,6 +755,7 @@
         service = CalDAVService(logObserver)
 
         rootResource = getRootResource(config, store, additional)
+        service.rootResource = rootResource
 
         underlyingSite = Site(rootResource)
         
@@ -838,11 +892,21 @@
     def _allBindAddresses(self):
         """
         An empty array for the config value of BindAddresses should be
-        equivalent a BindAddresses with a single empty string, meaning "bind
-        everything".
+        equivalent to an array containing two BindAddresses; one with a single
+        empty string, and one with "::", meaning "bind everything on both IPv4
+        and IPv6".
         """
         if not config.BindAddresses:
-            config.BindAddresses = [""]
+            if getattr(socket, "has_ipv6", False):
+                if conflictBetweenIPv4AndIPv6():
+                    # If there's a conflict between v4 and v6, then almost by
+                    # definition, v4 is mapped into the v6 space, so we will
+                    # listen "only" on v6.
+                    config.BindAddresses = ["::"]
+                else:
+                    config.BindAddresses = ["", "::"]
+            else:
+                config.BindAddresses = [""]
         return config.BindAddresses
 
 
@@ -1116,6 +1180,26 @@
         statsService.setName("stats")
         statsService.setServiceParent(s)
 
+        # Optionally enable Manhole access
+        if config.Manhole.Enabled:
+            try:
+                from twisted.conch.manhole_tap import makeService as manholeMakeService
+                portString = "tcp:%d:interface=127.0.0.1" % (config.Manhole.StartingPortNumber,)
+                manholeService = manholeMakeService({
+                    "sshPort" : None,
+                    "telnetPort" : portString,
+                    "namespace" : {
+                        "config" : config,
+                        "service" : s,
+                        },
+                    "passwd" : config.Manhole.PasswordFilePath,
+                })
+                manholeService.setServiceParent(s)
+                # Using print because logging isn't ready at this point
+                print "Manhole access enabled: %s" % (portString,)
+            except ImportError:
+                print "Manhole access could not enabled because manhole_tap could not be imported"
+
         # Finally, let's get the real show on the road.  Create a service that
         # will spawn all of our worker processes when started, and wrap that
         # service in zero to two necessary layers before it's started: first,

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/test/test_caldav.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/test/test_caldav.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/test/test_caldav.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,1229 +0,0 @@
-##
-# Copyright (c) 2007-2010 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
-import os
-import stat
-import grp
-
-from os.path import dirname, abspath
-
-from zope.interface import implements
-
-from twisted.python.threadable import isInIOThread
-from twisted.internet.reactor import callFromThread
-from twisted.python.usage import Options, UsageError
-from twisted.python.reflect import namedAny
-from twisted.python import log
-
-from twisted.internet.interfaces import IProcessTransport, IReactorProcess
-from twisted.internet.protocol import ServerFactory
-from twisted.internet.defer import Deferred
-from twisted.internet.task import Clock
-
-from twisted.application.service import IService, IServiceCollection
-from twisted.application import internet
-
-from twext.web2.dav import auth
-from twext.web2.log import LogWrapperResource
-from twext.python.filepath import CachingFilePath as FilePath
-
-from twext.python.plistlib import writePlist #@UnresolvedImport
-from twext.internet.tcp import MaxAcceptTCPServer, MaxAcceptSSLServer
-
-from twistedcaldav.config import config, ConfigDict
-from twistedcaldav.stdconfig import DEFAULT_CONFIG
-
-from twistedcaldav.directory.aggregate import AggregateDirectoryService
-from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.test.util import TestCase
-
-from calendarserver.tap.caldav import (
-    CalDAVOptions, CalDAVServiceMaker, CalDAVService, GroupOwnedUNIXServer,
-    DelayedStartupProcessMonitor, DelayedStartupLineLogger, TwistdSlaveProcess
-)
-from calendarserver.provision.root import RootResource
-from StringIO import StringIO
-
-
-# Points to top of source tree.
-sourceRoot = dirname(dirname(dirname(dirname(abspath(__file__)))))
-
-
-class NotAProcessTransport(object):
-    """
-    Simple L{IProcessTransport} stub.
-    """
-    implements(IProcessTransport)
-
-    def __init__(self, processProtocol, executable, args, env, path,
-                 uid, gid, usePTY, childFDs):
-        """
-        Hold on to all the attributes passed to spawnProcess.
-        """
-        self.processProtocol = processProtocol
-        self.executable = executable
-        self.args = args
-        self.env = env
-        self.path = path
-        self.uid = uid
-        self.gid = gid
-        self.usePTY = usePTY
-        self.childFDs = childFDs
-
-
-class InMemoryProcessSpawner(Clock):
-    """
-    Stub out L{IReactorProcess} and L{IReactorClock} so that we can examine the
-    interaction of L{DelayedStartupProcessMonitor} and the reactor.
-    """
-    implements(IReactorProcess)
-
-    def __init__(self):
-        """
-        Create some storage to hold on to all the fake processes spawned.
-        """
-        super(InMemoryProcessSpawner, self).__init__()
-        self.processTransports = []
-        self.waiting = []
-
-
-    def waitForOneProcess(self, amount=10.0):
-        """
-        Wait for an L{IProcessTransport} to be created by advancing the clock.
-        If none are created in the specified amount of time, raise an
-        AssertionError.
-        """
-        self.advance(amount)
-        if self.processTransports:
-            return self.processTransports.pop(0)
-        else:
-            raise AssertionError(
-                "There were no process transports available.  Calls: " +
-                repr(self.calls)
-            )
-
-
-    def spawnProcess(self, processProtocol, executable, args=(), env={},
-                     path=None, uid=None, gid=None, usePTY=0,
-                     childFDs=None):
-
-        transport = NotAProcessTransport(
-            processProtocol, executable, args, env, path, uid, gid, usePTY,
-            childFDs
-        )
-        transport.startedAt = self.seconds()
-        self.processTransports.append(transport)
-        if self.waiting:
-            self.waiting.pop(0).callback(transport)
-        return transport
-
-
-
-class TestCalDAVOptions (CalDAVOptions):
-    """
-    A fake implementation of CalDAVOptions that provides
-    empty implementations of checkDirectory and checkFile.
-    """
-    def checkDirectory(self, *args, **kwargs):
-        pass
-
-    def checkFile(self, *args, **kwargs):
-        pass
-
-
-    def loadConfiguration(self):
-        """
-        Simple wrapper to avoid printing during test runs.
-        """
-        oldout = sys.stdout
-        newout = sys.stdout = StringIO()
-        try:
-            return CalDAVOptions.loadConfiguration(self)
-        finally:
-            sys.stdout = oldout
-            log.msg(
-                "load configuration console output: %s" % newout.getvalue()
-            )
-
-
-class CalDAVOptionsTest (TestCase):
-    """
-    Test various parameters of our usage.Options subclass
-    """
-    def setUp(self):
-        """
-        Set up our options object, giving it a parent, and forcing the
-        global config to be loaded from defaults.
-        """
-        TestCase.setUp(self)
-        self.config = TestCalDAVOptions()
-        self.config.parent = Options()
-        self.config.parent["uid"] = 0
-        self.config.parent["gid"] = 0
-        self.config.parent["nodaemon"] = False
-
-    def tearDown(self):
-        config.setDefaults(DEFAULT_CONFIG)
-        config.reload()
-
-    def test_overridesConfig(self):
-        """
-        Test that values on the command line's -o and --option options
-        overide the config file
-        """
-        myConfig = ConfigDict(DEFAULT_CONFIG)
-        myConfigFile = self.mktemp()
-        writePlist(myConfig, myConfigFile)
-
-        argv = [
-            "-f", myConfigFile,
-            "-o", "EnableSACLs",
-            "-o", "HTTPPort=80",
-            "-o", "BindAddresses=127.0.0.1,127.0.0.2,127.0.0.3",
-            "-o", "DocumentRoot=/dev/null",
-            "-o", "UserName=None",
-            "-o", "EnableProxyPrincipals=False",
-        ]
-
-        self.config.parseOptions(argv)
-
-        self.assertEquals(config.EnableSACLs, True)
-        self.assertEquals(config.HTTPPort, 80)
-        self.assertEquals(config.BindAddresses,
-                          ["127.0.0.1", "127.0.0.2", "127.0.0.3"])
-        self.assertEquals(config.DocumentRoot, "/dev/null")
-        self.assertEquals(config.UserName, None)
-        self.assertEquals(config.EnableProxyPrincipals, False)
-
-        argv = ["-o", "Authentication=This Doesn't Matter"]
-
-        self.assertRaises(UsageError, self.config.parseOptions, argv)
-
-    def test_setsParent(self):
-        """
-        Test that certain values are set on the parent (i.e. twistd's
-        Option's object)
-        """
-        myConfig = ConfigDict(DEFAULT_CONFIG)
-        myConfigFile = self.mktemp()
-        writePlist(myConfig, myConfigFile)
-
-        argv = [
-            "-f", myConfigFile,
-            "-o", "PIDFile=/dev/null",
-        ]
-
-        self.config.parseOptions(argv)
-
-        self.assertEquals(self.config.parent["pidfile"], "/dev/null")
-
-    def test_specifyConfigFile(self):
-        """
-        Test that specifying a config file from the command line
-        loads the global config with those values properly.
-        """
-        myConfig = ConfigDict(DEFAULT_CONFIG)
-
-        myConfig.Authentication.Basic.Enabled = False
-        myConfig.HTTPPort = 80
-        myConfig.ServerHostName = "calendar.calenderserver.org"
-
-        myConfigFile = self.mktemp()
-        writePlist(myConfig, myConfigFile)
-
-        args = ["-f", myConfigFile]
-
-        self.config.parseOptions(args)
-
-        self.assertEquals(config.ServerHostName, myConfig["ServerHostName"])
-        self.assertEquals(config.HTTPPort, myConfig.HTTPPort)
-        self.assertEquals(
-            config.Authentication.Basic.Enabled,
-            myConfig.Authentication.Basic.Enabled
-        )
-
-    def test_specifyDictPath(self):
-        """
-        Test that we can specify command line overrides to leafs using
-        a "/" seperated path.  Such as "-o MultiProcess/ProcessCount=1"
-        """
-        myConfig = ConfigDict(DEFAULT_CONFIG)
-        myConfigFile = self.mktemp()
-        writePlist(myConfig, myConfigFile)
-
-        argv = [
-            "-o", "MultiProcess/ProcessCount=102",
-            "-f", myConfigFile,
-        ]
-
-        self.config.parseOptions(argv)
-
-        self.assertEquals(config.MultiProcess["ProcessCount"], 102)
-
-class BaseServiceMakerTests(TestCase):
-    """
-    Utility class for ServiceMaker tests.
-    """
-    configOptions = None
-
-    def setUp(self):
-        TestCase.setUp(self)
-        self.options = TestCalDAVOptions()
-        self.options.parent = Options()
-        self.options.parent["gid"] = None
-        self.options.parent["uid"] = None
-        self.options.parent["nodaemon"] = None
-
-        self.config = ConfigDict(DEFAULT_CONFIG)
-
-        accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml")
-        resourcesFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/resources.xml")
-        augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml")
-        pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem")
-
-        self.config["DirectoryService"] = {
-            "params": {"xmlFile": accountsFile},
-            "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService"
-        }
-
-        self.config["ResourceService"] = {
-            "params": {"xmlFile": resourcesFile},
-        }
-
-        self.config["AugmentService"] = {
-            "params": {"xmlFiles": [augmentsFile]},
-            "type": "twistedcaldav.directory.augment.AugmentXMLDB"
-        }
-
-        self.config.UseDatabase    = False
-        self.config.ServerRoot     = self.mktemp()
-        self.config.ConfigRoot     = "config"
-        self.config.ProcessType    = "Single"
-        self.config.SSLPrivateKey  = pemFile
-        self.config.SSLCertificate = pemFile
-        self.config.EnableSSL      = True
-        self.config.Memcached.Pools.Default.ClientEnabled = False
-        self.config.Memcached.Pools.Default.ServerEnabled = False
-        self.config.DirectoryAddressBook.Enabled = False
-
-        self.config.SudoersFile = ""
-
-        if self.configOptions:
-            self.config.update(self.configOptions)
-
-        os.mkdir(self.config.ServerRoot)
-        os.mkdir(os.path.join(self.config.ServerRoot, self.config.DocumentRoot))
-        os.mkdir(os.path.join(self.config.ServerRoot, self.config.DataRoot))
-        os.mkdir(os.path.join(self.config.ServerRoot, self.config.ConfigRoot))
-
-        self.configFile = self.mktemp()
-
-        self.writeConfig()
-
-
-    def tearDown(self):
-        config.setDefaults(DEFAULT_CONFIG)
-        config.reset()
-
-
-    def writeConfig(self):
-        """
-        Flush self.config out to self.configFile
-        """
-        writePlist(self.config, self.configFile)
-
-
-    def makeService(self):
-        """
-        Create a service by calling into CalDAVServiceMaker with
-        self.configFile
-        """
-        self.options.parseOptions(["-f", self.configFile])
-
-        return CalDAVServiceMaker().makeService(self.options)
-
-
-    def getSite(self):
-        """
-        Get the server.Site from the service by finding the HTTPFactory.
-        """
-        service = self.makeService()
-        for listeningService in inServiceHierarchy(
-                service,
-                # FIXME: need a better predicate for 'is this really an HTTP
-                # factory' but this works for now.
-                # NOTE: in a database 'single' configuration, PostgresService
-                # will prevent the HTTP services from actually getting added to
-                # the hierarchy until the hierarchy has started.
-                lambda x: hasattr(x, 'args')
-            ):
-            return listeningService.args[1].protocolArgs['requestFactory']
-        raise RuntimeError("No site found.")
-
-
-
-def inServiceHierarchy(svc, predicate):
-    """
-    Find services in the service collection which satisfy the given predicate.
-    """
-    for subsvc in svc.services:
-        if IServiceCollection.providedBy(subsvc):
-            for value in inServiceHierarchy(subsvc, predicate):
-                yield value
-        if predicate(subsvc):
-            yield subsvc
-
-
-
-def determineAppropriateGroupID():
-    """
-    Determine a secondary group ID which can be used for testing.
-    """
-    return os.getgroups()[1]
-
-
-
-class SocketGroupOwnership(TestCase):
-    """
-    Tests for L{GroupOwnedUNIXServer}.
-    """
-
-    def test_groupOwnedUNIXSocket(self):
-        """
-        When a L{GroupOwnedUNIXServer} is started, it will change the group of
-        its socket.
-        """
-        alternateGroup = determineAppropriateGroupID()
-        socketName = self.mktemp()
-        gous = GroupOwnedUNIXServer(alternateGroup, socketName, ServerFactory(), mode=0660)
-        gous.privilegedStartService()
-        self.addCleanup(gous.stopService)
-        filestat = os.stat(socketName)
-        self.assertTrue(stat.S_ISSOCK(filestat.st_mode))
-        self.assertEquals(filestat.st_gid, alternateGroup)
-        self.assertEquals(filestat.st_uid, os.getuid())
-
-
-
-class CalDAVServiceMakerTests(BaseServiceMakerTests):
-    """
-    Test the service maker's behavior
-    """
-
-    def test_makeServiceDispatcher(self):
-        """
-        Test the default options of the dispatching makeService
-        """
-        validServices = ["Slave", "Combined"]
-
-        self.config["HTTPPort"] = 0
-
-        for service in validServices:
-            self.config["ProcessType"] = service
-            self.writeConfig()
-            self.makeService()
-
-        self.config["ProcessType"] = "Unknown Service"
-        self.writeConfig()
-        self.assertRaises(UsageError, self.makeService)
-
-
-    def test_modesOnUNIXSockets(self):
-        """
-        The logging and stats UNIX sockets that are bound as part of the
-        'Combined' service hierarchy should have a secure mode specified: only
-        the executing user should be able to open and send to them.
-        """
-
-        self.config["HTTPPort"] = 0 # Don't conflict with the test above.
-        alternateGroup = determineAppropriateGroupID()
-        self.config.GroupName = grp.getgrgid(alternateGroup).gr_name
-
-        self.config["ProcessType"] = "Combined"
-        self.writeConfig()
-        svc = self.makeService()
-        for serviceName in ["logging"]:
-            socketService = svc.getServiceNamed(serviceName)
-            self.assertIsInstance(socketService, GroupOwnedUNIXServer)
-            m = socketService.kwargs.get("mode", 0666)
-            self.assertEquals(
-                m, int("660", 8),
-                "Wrong mode on %s: %s" % (serviceName, oct(m))
-            )
-            self.assertEquals(socketService.gid, alternateGroup)
-        for serviceName in ["stats"]:
-            socketService = svc.getServiceNamed(serviceName)
-            self.assertIsInstance(socketService, GroupOwnedUNIXServer)
-            m = socketService.kwargs.get("mode", 0444)
-            self.assertEquals(
-                m, int("440", 8),
-                "Wrong mode on %s: %s" % (serviceName, oct(m))
-            )
-            self.assertEquals(socketService.gid, alternateGroup)
-
-
-    def test_processMonitor(self):
-        """
-        In the master, there should be exactly one
-        L{DelayedStartupProcessMonitor} in the service hierarchy so that it
-        will be started by startup.
-        """
-        self.config["ProcessType"] = "Combined"
-        self.writeConfig()
-        self.assertEquals(
-            1,
-            len(
-                list(inServiceHierarchy(
-                    self.makeService(),
-                    lambda x: isinstance(x, DelayedStartupProcessMonitor)))
-            )
-        )
-
-
-
-
-class SlaveServiceTest(BaseServiceMakerTests):
-    """
-    Test various configurations of the Slave service
-    """
-
-    configOptions = {
-        "HTTPPort": 8008,
-        "SSLPort": 8443,
-    }
-
-    def test_defaultService(self):
-        """
-        Test the value of a Slave service in it's simplest
-        configuration.
-        """
-        service = self.makeService()
-
-        self.failUnless(
-            IService(service),
-            "%s does not provide IService" % (service,)
-        )
-        self.failUnless(
-            service.services,
-            "No services configured"
-        )
-        self.failUnless(
-            isinstance(service, CalDAVService),
-            "%s is not a CalDAVService" % (service,)
-        )
-
-    def test_defaultListeners(self):
-        """
-        Test that the Slave service has sub services with the
-        default TCP and SSL configuration
-        """
-        service = self.makeService()
-
-        expectedSubServices = dict((
-            (MaxAcceptTCPServer, self.config["HTTPPort"]),
-            (MaxAcceptSSLServer, self.config["SSLPort"]),
-        ))
-
-        configuredSubServices = [(s.__class__, getattr(s, 'args', None))
-                                 for s in service.services]
-        checked = 0
-        for serviceClass, serviceArgs in configuredSubServices:
-            if serviceClass in expectedSubServices:
-                checked += 1
-                self.assertEquals(
-                    serviceArgs[0],
-                    dict(expectedSubServices)[serviceClass]
-                )
-        self.assertEquals(checked, len(expectedSubServices))
-
-
-    def test_SSLKeyConfiguration(self):
-        """
-        Test that the configuration of the SSLServer reflect the config file's
-        SSL Private Key and SSL Certificate
-        """
-        service = self.makeService()
-
-        sslService = None
-        for s in service.services:
-            if isinstance(s, internet.SSLServer):
-                sslService = s
-                break
-
-        self.failIf(sslService is None, "No SSL Service found")
-
-        context = sslService.args[2]
-
-        self.assertEquals(
-            self.config["SSLPrivateKey"],
-            context.privateKeyFileName
-        )
-        self.assertEquals(
-            self.config["SSLCertificate"],
-            context.certificateFileName,
-        )
-
-    def test_noSSL(self):
-        """
-        Test the single service to make sure there is no SSL Service when SSL
-        is disabled
-        """
-        del self.config["SSLPort"]
-        self.writeConfig()
-
-        service = self.makeService()
-
-        self.assertNotIn(
-            internet.SSLServer,
-            [s.__class__ for s in service.services]
-        )
-
-    def test_noHTTP(self):
-        """
-        Test the single service to make sure there is no TCPServer when
-        HTTPPort is not configured
-        """
-        del self.config["HTTPPort"]
-        self.writeConfig()
-
-        service = self.makeService()
-
-        self.assertNotIn(
-            internet.TCPServer,
-            [s.__class__ for s in service.services]
-        )
-
-    def test_singleBindAddresses(self):
-        """
-        Test that the TCPServer and SSLServers are bound to the proper address
-        """
-        self.config.BindAddresses = ["127.0.0.1"]
-        self.writeConfig()
-
-        service = self.makeService()
-
-        for s in service.services:
-            if isinstance(s, (internet.TCPServer, internet.SSLServer)):
-                self.assertEquals(s.kwargs["interface"], "127.0.0.1")
-
-    def test_multipleBindAddresses(self):
-        """
-        Test that the TCPServer and SSLServers are bound to the proper
-        addresses.
-        """
-        self.config.BindAddresses = [
-            "127.0.0.1",
-            "10.0.0.2",
-            "172.53.13.123",
-        ]
-
-        self.writeConfig()
-        service = self.makeService()
-
-        tcpServers = []
-        sslServers = []
-
-        for s in service.services:
-            if isinstance(s, internet.TCPServer):
-                tcpServers.append(s)
-            elif isinstance(s, internet.SSLServer):
-                sslServers.append(s)
-
-        self.assertEquals(len(tcpServers), len(self.config.BindAddresses))
-        self.assertEquals(len(sslServers), len(self.config.BindAddresses))
-
-        for addr in self.config.BindAddresses:
-            for s in tcpServers:
-                if s.kwargs["interface"] == addr:
-                    tcpServers.remove(s)
-
-            for s in sslServers:
-                if s.kwargs["interface"] == addr:
-                    sslServers.remove(s)
-
-        self.assertEquals(len(tcpServers), 0)
-        self.assertEquals(len(sslServers), 0)
-
-    def test_listenBacklog(self):
-        """
-        Test that the backlog arguments is set in TCPServer and SSLServers
-        """
-        self.config.ListenBacklog = 1024
-        self.writeConfig()
-        service = self.makeService()
-
-        for s in service.services:
-            if isinstance(s, (internet.TCPServer, internet.SSLServer)):
-                self.assertEquals(s.kwargs["backlog"], 1024)
-
-
-class ServiceHTTPFactoryTests(BaseServiceMakerTests):
-    """
-    Test the configuration of the initial resource hierarchy of the
-    single service
-    """
-    configOptions = {"HTTPPort": 8008}
-
-    def test_AuthWrapperAllEnabled(self):
-        """
-        Test the configuration of the authentication wrapper
-        when all schemes are enabled.
-        """
-        self.config.Authentication.Digest.Enabled = True
-        self.config.Authentication.Kerberos.Enabled = True
-        self.config.Authentication.Kerberos.ServicePrincipal = "http/hello at bob"
-        self.config.Authentication.Basic.Enabled = True
-
-        self.writeConfig()
-        site = self.getSite()
-
-        self.failUnless(isinstance(
-                site.resource.resource,
-                auth.AuthenticationWrapper))
-
-        authWrapper = site.resource.resource
-
-        expectedSchemes = ["negotiate", "digest", "basic"]
-
-        for scheme in authWrapper.credentialFactories:
-            self.failUnless(scheme in expectedSchemes)
-
-        self.assertEquals(len(expectedSchemes),
-                          len(authWrapper.credentialFactories))
-
-    def test_servicePrincipalNone(self):
-        """
-        Test that the Kerberos principal look is attempted if the principal is empty.
-        """
-        self.config.Authentication.Kerberos.ServicePrincipal = ""
-        self.config.Authentication.Kerberos.Enabled = True
-        self.writeConfig()
-        site = self.getSite()
-
-        authWrapper = site.resource.resource
-
-        self.assertFalse(authWrapper.credentialFactories.has_key("negotiate"))
-
-    def test_servicePrincipal(self):
-        """
-        Test that the kerberos realm is the realm portion of a principal
-        in the form proto/host at realm
-        """
-        self.config.Authentication.Kerberos.ServicePrincipal = "http/hello at bob"
-        self.config.Authentication.Kerberos.Enabled = True
-        self.writeConfig()
-        site = self.getSite()
-
-        authWrapper = site.resource.resource
-        ncf = authWrapper.credentialFactories["negotiate"]
-
-        self.assertEquals(ncf.service, "http at HELLO")
-        self.assertEquals(ncf.realm, "bob")
-
-    def test_AuthWrapperPartialEnabled(self):
-        """
-        Test that the expected credential factories exist when
-        only a partial set of authentication schemes is
-        enabled.
-        """
-
-        self.config.Authentication.Basic.Enabled    = False
-        self.config.Authentication.Kerberos.Enabled = False
-
-        self.writeConfig()
-        site = self.getSite()
-
-        authWrapper = site.resource.resource
-
-        expectedSchemes = ["digest"]
-
-        for scheme in authWrapper.credentialFactories:
-            self.failUnless(scheme in expectedSchemes)
-
-        self.assertEquals(
-            len(expectedSchemes),
-            len(authWrapper.credentialFactories)
-        )
-
-    def test_LogWrapper(self):
-        """
-        Test the configuration of the log wrapper
-        """
-        site = self.getSite()
-
-        self.failUnless(isinstance(
-                site.resource,
-                LogWrapperResource))
-
-    def test_rootResource(self):
-        """
-        Test the root resource
-        """
-        site = self.getSite()
-        root = site.resource.resource.resource
-
-        self.failUnless(isinstance(root, RootResource))
-
-    def test_principalResource(self):
-        """
-        Test the principal resource
-        """
-        site = self.getSite()
-        root = site.resource.resource.resource
-
-        self.failUnless(isinstance(
-            root.getChild("principals"),
-            DirectoryPrincipalProvisioningResource
-        ))
-
-    def test_calendarResource(self):
-        """
-        Test the calendar resource
-        """
-        site = self.getSite()
-        root = site.resource.resource.resource
-
-        self.failUnless(isinstance(
-            root.getChild("calendars"),
-            DirectoryCalendarHomeProvisioningResource
-        ))
-
-
-sudoersFile = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-    <key>users</key>
-    <array>
-       	<dict>
-            <key>password</key>
-            <string>superuser</string>
-            <key>username</key>
-            <string>superuser</string>
-        </dict>
-    </array>
-</dict>
-</plist>
-"""
-
-class DirectoryServiceTest(BaseServiceMakerTests):
-    """
-    Tests of the directory service
-    """
-
-    configOptions = {"HTTPPort": 8008}
-
-    def test_sameDirectory(self):
-        """
-        Test that the principal hierarchy has a reference
-        to the same DirectoryService as the calendar hierarchy
-        """
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild("principals")
-        calendars = site.resource.resource.resource.getChild("calendars")
-
-        self.assertEquals(principals.directory, calendars.directory)
-
-    def test_aggregateDirectory(self):
-        """
-        Assert that the base directory service is actually
-        an AggregateDirectoryService
-        """
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild("principals")
-        directory = principals.directory
-
-        self.failUnless(isinstance(directory, AggregateDirectoryService))
-
-    def test_sudoDirectoryService(self):
-        """
-        Test that a sudo directory service is available if the
-        SudoersFile is set and exists
-        """
-        self.config.SudoersFile = "sudoers.plist"
-        sudoersFilePath = os.path.join(
-            self.config.ServerRoot,
-            self.config.ConfigRoot,
-            self.config.SudoersFile
-        )
-        self.writeConfig()
-
-        open(sudoersFilePath, "w").write(sudoersFile)
-
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild("principals")
-        directory = principals.directory
-
-        self.failUnless(sudoersFilePath)
-
-        sudoService = directory.serviceForRecordType(SudoDirectoryService.recordType_sudoers)
-
-        self.assertEquals(
-            sudoService.plistFile.path,
-            os.path.abspath(sudoersFilePath)
-        )
-        self.failUnless(
-            SudoDirectoryService.recordType_sudoers
-            in directory.userRecordTypes
-        )
-
-    def test_sudoDirectoryServiceNoFile(self):
-        """
-        Test that there is no SudoDirectoryService if
-        the SudoersFile does not exist.
-        """
-        self.config.SudoersFile = self.mktemp()
-
-        self.writeConfig()
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild("principals")
-        directory = principals.directory
-
-        self.failUnless(self.config.SudoersFile)
-
-        self.assertRaises(
-            UnknownRecordTypeError,
-            directory.serviceForRecordType,
-            SudoDirectoryService.recordType_sudoers
-        )
-
-    def test_sudoDirectoryServiceNotConfigured(self):
-        """
-        Test that there is no SudoDirectoryService if
-        the SudoersFile is not configured
-        """
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild("principals")
-        directory = principals.directory
-
-        self.failIf(self.config.SudoersFile)
-
-        self.assertRaises(
-            UnknownRecordTypeError,
-            directory.serviceForRecordType,
-            SudoDirectoryService.recordType_sudoers
-        )
-
-    def test_configuredDirectoryService(self):
-        """
-        Test that the real directory service is the directory service
-        set in the configuration file.
-        """
-        site = self.getSite()
-        principals = site.resource.resource.resource.getChild("principals")
-        directory = principals.directory
-
-        realDirectory = directory.serviceForRecordType("users")
-
-        configuredDirectory = namedAny(self.config.DirectoryService.type)
-
-        self.failUnless(isinstance(realDirectory, configuredDirectory))
-
-
-
-class DummyProcessObject(object):
-    """
-    Simple stub for Process Object API which just has an executable and some
-    arguments.
-
-    This is a stand in for L{TwistdSlaveProcess}.
-    """
-
-    def __init__(self, scriptname, *args):
-        self.scriptname = scriptname
-        self.args = args
-
-
-    def getCommandLine(self):
-        """
-        Simple command line.
-        """
-        return [self.scriptname] + list(self.args)
-
-
-    def getFileDescriptors(self):
-        """
-        Return a dummy, empty mapping of file descriptors.
-        """
-        return {}
-
-
-    def getName(self):
-        """
-        Get a dummy name.
-        """
-        return 'Dummy'
-
-
-class ScriptProcessObject(DummyProcessObject):
-    """
-    Simple stub for the Process Object API that will run a test script.
-    """
-
-    def getCommandLine(self):
-        """
-        Get the command line to invoke this script.
-        """
-        return [
-            sys.executable,
-            FilePath(__file__).sibling(self.scriptname).path
-        ] + list(self.args)
-
-
-
-
-
-class DelayedStartupProcessMonitorTests(TestCase):
-    """
-    Test cases for L{DelayedStartupProcessMonitor}.
-    """
-
-    def test_lineAfterLongLine(self):
-        """
-        A "long" line of output from a monitored process (longer than
-        L{LineReceiver.MAX_LENGTH}) should be logged in chunks rather than all
-        at once, to avoid resource exhaustion.
-        """
-        dspm = DelayedStartupProcessMonitor()
-        dspm.addProcessObject(ScriptProcessObject(
-                'longlines.py', str(DelayedStartupLineLogger.MAX_LENGTH)),
-                          os.environ)
-        dspm.startService()
-        self.addCleanup(dspm.stopService)
-
-        logged = []
-
-        def tempObserver(event):
-            # Probably won't be a problem, but let's not have any intermittent
-            # test issues that stem from multi-threaded log messages randomly
-            # going off...
-            if not isInIOThread():
-                callFromThread(tempObserver, event)
-                return
-            if event.get('isError'):
-                d.errback()
-            m = event.get('message')[0]
-            if m.startswith('[Dummy] '):
-                logged.append(event)
-                if m == '[Dummy] z':
-                    d.callback("done")
-
-        log.addObserver(tempObserver)
-        self.addCleanup(log.removeObserver, tempObserver)
-        d = Deferred()
-        def assertions(result):
-            self.assertEquals(["[Dummy] x",
-                               "[Dummy] y",
-                               "[Dummy] y", # final segment
-                               "[Dummy] z"],
-                              [''.join(evt['message'])[:len('[Dummy]') + 2]
-                               for evt in logged])
-            self.assertEquals([" (truncated, continued)",
-                               " (truncated, continued)",
-                               "[Dummy] y",
-                               "[Dummy] z"],
-                              [''.join(evt['message'])[-len(" (truncated, continued)"):]
-                               for evt in logged])
-        d.addCallback(assertions)
-        return d
-
-
-    def test_breakLineIntoSegments(self):
-        """
-        Exercise the line-breaking logic with various key lengths
-        """
-        testLogger = DelayedStartupLineLogger()
-        testLogger.MAX_LENGTH = 10
-        for input, output in [
-            ("", []),
-            ("a", ["a"]),
-            ("abcde", ["abcde"]),
-            ("abcdefghij", ["abcdefghij"]),
-            ("abcdefghijk",
-                ["abcdefghij (truncated, continued)",
-                 "k"
-                ]
-            ),
-            ("abcdefghijklmnopqrst",
-                ["abcdefghij (truncated, continued)",
-                 "klmnopqrst"
-                ]
-            ),
-            ("abcdefghijklmnopqrstuv",
-                ["abcdefghij (truncated, continued)",
-                 "klmnopqrst (truncated, continued)",
-                 "uv"]
-            ),
-        ]:
-            self.assertEquals(output, testLogger._breakLineIntoSegments(input))
-
-
-    def test_acceptDescriptorInheritance(self):
-        """
-        If a L{TwistdSlaveProcess} specifies some file descriptors to be
-        inherited, they should be inherited by the subprocess.
-        """
-        imps = InMemoryProcessSpawner()
-        dspm = DelayedStartupProcessMonitor(imps)
-
-        # Most arguments here will be ignored, so these are bogus values.
-        slave = TwistdSlaveProcess(
-            twistd        = "bleh",
-            tapname       = "caldav",
-            configFile    = "/does/not/exist",
-            id            = 10,
-            interfaces    = '127.0.0.1',
-            inheritFDs    = [3, 7],
-            inheritSSLFDs = [19, 25],
-        )
-
-        dspm.addProcessObject(slave, {})
-        dspm.startService()
-        # We can easily stub out spawnProcess, because caldav calls it, but a
-        # bunch of callLater calls are buried in procmon itself, so we need to
-        # use the real clock.
-        oneProcessTransport = imps.waitForOneProcess()
-        self.assertEquals(oneProcessTransport.childFDs,
-                          {0: 'w', 1: 'r', 2: 'r',
-                           3: 3, 7: 7,
-                           19: 19, 25: 25})
-
-
-    def test_changedArgumentEachSpawn(self):
-        """
-        If the result of C{getCommandLine} changes on subsequent calls,
-        subsequent calls should result in different arguments being passed to
-        C{spawnProcess} each time.
-        """
-        imps = InMemoryProcessSpawner()
-        dspm = DelayedStartupProcessMonitor(imps)
-        slave = DummyProcessObject('scriptname', 'first')
-        dspm.addProcessObject(slave, {})
-        dspm.startService()
-        oneProcessTransport = imps.waitForOneProcess()
-        self.assertEquals(oneProcessTransport.args,
-                          ['scriptname', 'first'])
-        slave.args = ['second']
-        oneProcessTransport.processProtocol.processEnded(None)
-        twoProcessTransport = imps.waitForOneProcess()
-        self.assertEquals(twoProcessTransport.args,
-                          ['scriptname', 'second'])
-
-
-    def test_metaDescriptorInheritance(self):
-        """
-        If a L{TwistdSlaveProcess} specifies a meta-file-descriptor to be
-        inherited, it should be inherited by the subprocess, and a
-        configuration argument should be passed that indicates to the
-        subprocess.
-        """
-        imps = InMemoryProcessSpawner()
-        dspm = DelayedStartupProcessMonitor(imps)
-        # Most arguments here will be ignored, so these are bogus values.
-        slave = TwistdSlaveProcess(
-            twistd     = "bleh",
-            tapname    = "caldav",
-            configFile = "/does/not/exist",
-            id         = 10,
-            interfaces = '127.0.0.1',
-            metaSocket = FakeDispatcher().addSocket()
-        )
-
-        dspm.addProcessObject(slave, {})
-        dspm.startService()
-        oneProcessTransport = imps.waitForOneProcess()
-        self.assertIn("MetaFD=4", oneProcessTransport.args)
-        self.assertEquals(
-            oneProcessTransport.args[oneProcessTransport.args.index("MetaFD=4")-1],
-            '-o',
-            "MetaFD argument was not passed as an option"
-        )
-        self.assertEquals(oneProcessTransport.childFDs,
-                          {0: 'w', 1: 'r', 2: 'r',
-                           4: 4})
-
-
-    def test_startServiceDelay(self):
-        """
-        Starting a L{DelayedStartupProcessMonitor} should result in the process
-        objects that have been added to it being started once per
-        delayInterval.
-        """
-        imps = InMemoryProcessSpawner()
-        dspm = DelayedStartupProcessMonitor(imps)
-        dspm.delayInterval = 3.0
-        sampleCounter = range(0, 5)
-        for counter in sampleCounter:
-            slave = TwistdSlaveProcess(
-                twistd     = "bleh",
-                tapname    = "caldav",
-                configFile = "/does/not/exist",
-                id         = counter * 10,
-                interfaces = '127.0.0.1',
-                metaSocket = FakeDispatcher().addSocket()
-            )
-            dspm.addProcessObject(slave, {"SAMPLE_ENV_COUNTER": str(counter)})
-        dspm.startService()
-
-        # Advance the clock a bunch of times, allowing us to time things with a
-        # comprehensible resolution.
-        imps.pump([0] + [dspm.delayInterval / 2.0] * len(sampleCounter) * 3)
-        expectedValues = [dspm.delayInterval * n for n in sampleCounter]
-        self.assertEquals([x.startedAt for x in imps.processTransports],
-                          expectedValues)
-
-
-
-class FakeFD(object):
-
-    def __init__(self, n):
-        self.fd = n
-
-    
-    def fileno(self):
-        return self.fd
-
-
-
-class FakeDispatcher(object):
-    n = 3
-
-    def addSocket(self):
-        self.n += 1
-        return FakeFD(self.n)
-
-
-
-class TwistdSlaveProcessTests(TestCase):
-    """
-    Tests for L{TwistdSlaveProcess}.
-    """
-    def test_pidfile(self):
-        """
-        The result of L{TwistdSlaveProcess.getCommandLine} includes an option
-        setting the name of the pidfile to something including the instance id.
-        """
-        slave = TwistdSlaveProcess("/path/to/twistd", "something", "config", 7, [])
-        commandLine = slave.getCommandLine()
-
-        option = 'PIDFile=something-instance-7.pid'
-        self.assertIn(option, commandLine)
-        self.assertEquals(commandLine[commandLine.index(option) - 1], '-o')
-

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/util.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tap/util.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -53,12 +53,11 @@
 from twistedcaldav.directory.directory import GroupMembershipCache
 from twistedcaldav.directory.internal import InternalDirectoryService
 from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource
-from twistedcaldav.directory.sudo import SudoDirectoryService
 from twistedcaldav.directory.wiki import WikiDirectoryService
 from twistedcaldav.notify import NotifierFactory, getPubSubConfiguration
 from calendarserver.push.applepush import APNSubscriptionResource
 from twistedcaldav.directorybackedaddressbook import DirectoryBackedAddressBookResource
-from twistedcaldav.resource import CalDAVResource, AuthenticationWrapper
+from twistedcaldav.resource import AuthenticationWrapper
 from twistedcaldav.schedule import IScheduleInboxResource
 from twistedcaldav.simpleresource import SimpleResource, SimpleRedirectResource
 from twistedcaldav.timezones import TimezoneCache
@@ -237,6 +236,9 @@
             logSQL=config.LogDatabase.SQLStatements,
             logTransactionWaits=config.LogDatabase.TransactionWaitSeconds,
             timeoutTransactions=config.TransactionTimeoutSeconds,
+            cacheQueries=config.QueryCaching.Enabled,
+            cachePool=config.QueryCaching.MemcachedPool,
+            cacheExpireSeconds=config.QueryCaching.ExpireSeconds
         )
     else:
         return CommonFileDataStore(
@@ -312,25 +314,6 @@
         directories.append(resourceDirectory)
 
     #
-    # Add sudoers directory
-    #
-    sudoDirectory = None
-
-    if config.SudoersFile and os.path.exists(config.SudoersFile):
-        log.info("Configuring SudoDirectoryService with file: %s"
-                      % (config.SudoersFile,))
-
-        sudoDirectory = SudoDirectoryService(config.SudoersFile)
-        sudoDirectory.realmName = baseDirectory.realmName
-
-        CalDAVResource.sudoDirectory = sudoDirectory
-        directories.insert(0, sudoDirectory)
-    else:
-        log.info( "Not using SudoDirectoryService; file doesn't exist: %s"
-            % (config.SudoersFile,)
-        )
-
-    #
     # Add wiki directory service
     #
     if config.Authentication.Wiki.Enabled:
@@ -348,10 +331,6 @@
 
     directory = AggregateDirectoryService(directories, groupMembershipCache)
 
-    if sudoDirectory:
-        directory.userRecordTypes.insert(0,
-            SudoDirectoryService.recordType_sudoers)
-
     #
     # Use system-wide realm on OSX
     #
@@ -645,14 +624,10 @@
     #
     apnConfig = config.Notifications.Services["ApplePushNotifier"]
     if apnConfig.Enabled:
-        log.info("Setting up APNS resource at /%s with auth: %s" %
-            (apnConfig["SubscriptionURL"], apnConfig["AuthMechanisms"]))
-        resources.append((
-            apnConfig["SubscriptionURL"],
-            apnSubscriptionResourceClass,
-            [],
-            apnConfig["AuthMechanisms"]
-        ))
+        log.info("Setting up APNS resource at /%s" %
+            (apnConfig["SubscriptionURL"],))
+        apnResource = apnSubscriptionResourceClass(root, newStore)
+        root.putChild(apnConfig["SubscriptionURL"], apnResource)
 
     #
     # Configure ancillary data

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/calverify.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/calverify.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/calverify.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -38,9 +38,9 @@
 
 """
 
-from calendarserver.tap.util import directoryFromConfig
 from calendarserver.tools import tables
 from calendarserver.tools.cmdline import utilityMain
+from calendarserver.tools.util import getDirectory
 from pycalendar import definitions
 from pycalendar.calendar import PyCalendar
 from pycalendar.datetime import PyCalendarDateTime
@@ -56,6 +56,7 @@
 from twistedcaldav.ical import Component, ignoredComponents,\
     InvalidICalendarDataError
 from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from twistedcaldav.util import normalizationLookup
 from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
 from txdav.common.icommondatastore import InternalDataStoreError
 import collections
@@ -80,7 +81,7 @@
 description = '\n'.join(
     wordWrap(
         """
-        Usage: calendarserver_calverify [options] [input specifiers]\n
+        Usage: calendarserver_verify_data [options] [input specifiers]\n
         """,
         int(os.environ.get('COLUMNS', '80'))
     )
@@ -88,7 +89,7 @@
 
 class CalVerifyOptions(Options):
     """
-    Command-line options for 'calendarserver_calverify'
+    Command-line options for 'calendarserver_verify_data'
     """
 
     synopsis = description
@@ -148,6 +149,10 @@
         self.reactor = reactor
         self.config = config
         self._directory = None
+        
+        self.cuaCache = {}
+        
+        self.results = {}
 
 
     def startService(self):
@@ -182,14 +187,14 @@
         """
         Report on home collections for which there are no directory records. 
         """
-        print "\n---- Finding calendar homes with no directory record ----"
+        self.output.write("\n---- Finding calendar homes with no directory record ----\n")
         self.txn = self.store.newTransaction()
 
         if self.options["verbose"]:
             t = time.time()
         uids = yield self.getAllHomeUIDs()
         if self.options["verbose"]:
-            print "getAllHomeUIDs time: %.1fs" % (time.time() - t,)
+            self.output.write("getAllHomeUIDs time: %.1fs\n" % (time.time() - t,))
         missing = []
         wrong_server = []
         uids_len = len(uids)
@@ -197,11 +202,11 @@
 
         for ctr, uid in enumerate(uids):
             if self.options["verbose"] and divmod(ctr, uids_div)[1] == 0:
-                print "%d of %d (%d%%)" % (
+                self.output.write("%d of %d (%d%%)\n" % (
                     ctr+1,
                     uids_len,
                     ((ctr+1) * 100 / uids_len),
-                )
+                ))
 
             record = self.directoryService().recordWithGUID(uid)
             if record is None:
@@ -276,7 +281,7 @@
     @inlineCallbacks
     def doScan(self, ical, mismatch, fix):
         
-        print "\n---- Scanning calendar data ----"
+        self.output.write("\n---- Scanning calendar data ----\n")
 
         self.start = PyCalendarDateTime.getToday()
         self.start.setDateOnly(False)
@@ -308,8 +313,9 @@
         self.txn = None
 
         if self.options["verbose"]:
-            print "%s time: %.1fs" % (descriptor, time.time() - t,)
-        print "Number of events to process: %s" % (len(rows,))
+            self.output.write("%s time: %.1fs\n" % (descriptor, time.time() - t,))
+        self.output.write("Number of events to process: %s\n" % (len(rows,)))
+        self.results["Number of events to process"] = len(rows)
         
         # Split into organizer events and attendee events
         self.organized = []
@@ -325,8 +331,10 @@
                 self.attended.append((owner, resid, uid, md5, organizer, created, modified,))
                 self.attended_byuid[uid].append((owner, resid, uid, md5, organizer, created, modified,))
                 
-        print "Number of organizer events to process: %s" % (len(self.organized),)
-        print "Number of attendee events to process: %s" % (len(self.attended,))
+        self.output.write("Number of organizer events to process: %s\n" % (len(self.organized),))
+        self.output.write("Number of attendee events to process: %s\n" % (len(self.attended,)))
+        self.results["Number of organizer events to process"] = len(self.organized)
+        self.results["Number of attendee events to process"] = len(self.attended)
 
         if ical:
             yield self.calendarDataCheck(rows)
@@ -448,7 +456,7 @@
         Check each calendar resource for valid iCalendar data.
         """
 
-        print "\n---- Verifying each calendar object resource ----"
+        self.output.write("\n---- Verifying each calendar object resource ----\n")
         self.txn = self.store.newTransaction()
 
         if self.options["verbose"]:
@@ -466,9 +474,9 @@
             count += 1
             if self.options["verbose"]:
                 if count == 1:
-                    print "Bad/Current/Total"
+                    self.output.write("Bad/Current/Total\n")
                 if divmod(count, 100)[1] == 0:
-                    print "%s/%s/%s" % (badlen, count, total,)
+                    self.output.write("%s/%s/%s\n" % (badlen, count, total,))
             
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(count, 100)[1] == 0:
@@ -494,13 +502,15 @@
         self.output.write("\n")
         self.output.write("Bad iCalendar data (total=%d):\n" % (len(results_bad),))
         table.printTable(os=self.output)
+        
+        self.results["Bad iCalendar data"] = results_bad
          
         if self.options["verbose"]:
             diff_time = time.time() - t
-            print "Time: %.2f s  Average: %.1f ms/resource" % (
+            self.output.write("Time: %.2f s  Average: %.1f ms/resource\n" % (
                 diff_time,
                 (1000.0 * diff_time) / total,
-            )
+            ))
 
     errorPrefix = "Calendar data had unfixable problems:\n  "
 
@@ -541,15 +551,33 @@
 
     def noPrincipalPathCUAddresses(self, component, doFix):
         
+        def lookupFunction(cuaddr, principalFunction, config):
+    
+            # Return cached results, if any.
+            if self.cuaCache.has_key(cuaddr):
+                return self.cuaCache[cuaddr]
+    
+            result = normalizationLookup(cuaddr, principalFunction, config)
+    
+            # Cache the result
+            self.cuaCache[cuaddr] = result
+            return result
+
         for subcomponent in component.subcomponents():
             if subcomponent.name() in ignoredComponents:
                 continue
             organizer = subcomponent.getProperty("ORGANIZER")
             if organizer and organizer.value().startswith("http"):
-                raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
+                if doFix:
+                    component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                else:
+                    raise InvalidICalendarDataError("iCalendar ORGANIZER starts with 'http(s)'")
             for attendee in subcomponent.properties("ATTENDEE"):
                 if attendee.value().startswith("http"):
-                    raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'http(s)'")
+                    if doFix:
+                        component.normalizeCalendarUserAddresses(lookupFunction, self.directoryService().principalForCalendarUserAddress)
+                    else:
+                        raise InvalidICalendarDataError("iCalendar ATTENDEE starts with 'http(s)'")
 
     @inlineCallbacks
     def fixCalendarData(self, resid):
@@ -573,6 +601,7 @@
             component.validCalendarData(doFix=True, validateRecurrences=True)
             component.validCalendarForCalDAV(methodAllowed=False)
             component.validOrganizerForScheduling(doFix=True)
+            self.noPrincipalPathCUAddresses(component, doFix=True)
         except ValueError:
             result = False
             message = "Failed fix: "
@@ -594,7 +623,7 @@
         view of attendee status does not match the attendee's view of their own status.
         """
         
-        print "\n---- Verifying Organizer events against Attendee copies ----"
+        self.output.write("\n---- Verifying Organizer events against Attendee copies ----\n")
         self.txn = self.store.newTransaction()
 
         results_missing = []
@@ -607,13 +636,13 @@
         for ctr, organizerEvent in enumerate(self.organized):
             
             if self.options["verbose"] and divmod(ctr, organizer_div)[1] == 0:
-                print "%d of %d (%d%%) Missing: %d  Mismatched: %s" % (
+                self.output.write("%d of %d (%d%%) Missing: %d  Mismatched: %s\n" % (
                     ctr+1,
                     organized_len,
                     ((ctr+1) * 100 / organized_len),
                     len(results_missing),
                     len(results_mismatch),
-                )
+                ))
 
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(ctr, 100)[1] == 0:
@@ -626,7 +655,7 @@
             if calendar is None:
                 continue
             if self.options["verbose"] and self.masterComponent(calendar) is None:
-                print "Missing master for organizer: %s, resid: %s, uid: %s" % (organizer, resid, uid,)
+                self.output.write("Missing master for organizer: %s, resid: %s, uid: %s\n" % (organizer, resid, uid,))
             organizerViewOfAttendees = self.buildAttendeeStates(calendar, self.start, self.end)
             try:
                 del organizerViewOfAttendees[organizer]
@@ -667,11 +696,11 @@
                                 results_mismatch.append((uid, resid, organizer, org_created, org_modified, organizerAttendee, att_created, att_modified))
                                 broken = True
                                 if self.options["details"]:
-                                    print "Mismatch: on Organizer's side:"
-                                    print "          UID: %s" % (uid,)
-                                    print "          Organizer: %s" % (organizer,)
-                                    print "          Attendee: %s" % (organizerAttendee,)
-                                    print "          Instance: %s" % (_organizerInstance,)
+                                    self.output.write("Mismatch: on Organizer's side:\n")
+                                    self.output.write("          UID: %s\n" % (uid,))
+                                    self.output.write("          Organizer: %s\n" % (organizer,))
+                                    self.output.write("          Attendee: %s\n" % (organizerAttendee,))
+                                    self.output.write("          Instance: %s\n" % (_organizerInstance,))
                                 break
                         # Check that the difference is only cancelled on the attendees side
                         for _attendeeInstance, partstat in attendeeOwnStatus.difference(organizerViewOfStatus):
@@ -680,10 +709,10 @@
                                     results_mismatch.append((uid, resid, organizer, org_created, org_modified, organizerAttendee, att_created, att_modified))
                                 broken = True
                                 if self.options["details"]:
-                                    print "Mismatch: on Attendee's side:"
-                                    print "          Organizer: %s" % (organizer,)
-                                    print "          Attendee: %s" % (organizerAttendee,)
-                                    print "          Instance: %s" % (_attendeeInstance,)
+                                    self.output.write("Mismatch: on Attendee's side:\n")
+                                    self.output.write("          Organizer: %s\n" % (organizer,))
+                                    self.output.write("          Attendee: %s\n" % (organizerAttendee,))
+                                    self.output.write("          Instance: %s\n" % (_attendeeInstance,))
                                 break
 
                 # Check that the status for this attendee is always declined which means a missing copy of the event is OK
@@ -755,7 +784,7 @@
         Make sure that for each attendee, there is a matching event for the organizer.
         """
 
-        print "\n---- Verifying Attendee events against Organizer copies ----"
+        self.output.write("\n---- Verifying Attendee events against Organizer copies ----\n")
         self.txn = self.store.newTransaction()
 
         # Now try to match up each attendee event
@@ -767,13 +796,13 @@
         for ctr, attendeeEvent in enumerate(self.attended):
             
             if self.options["verbose"] and divmod(ctr, attended_div)[1] == 0:
-                print "%d of %d (%d%%) Missing: %d  Mismatched: %s" % (
+                self.output.write("%d of %d (%d%%) Missing: %d  Mismatched: %s\n" % (
                     ctr+1,
                     attended_len,
                     ((ctr+1) * 100 / attended_len),
                     len(missing),
                     len(mismatched),
-                )
+                ))
 
             # To avoid holding locks on all the rows scanned, commit every 100 resources
             if divmod(ctr, 100)[1] == 0:
@@ -980,7 +1009,7 @@
         configuration, creating one first if necessary.
         """
         if self._directory is None:
-            self._directory = directoryFromConfig(self.config)
+            self._directory = getDirectory(self.config) #directoryFromConfig(self.config)
         return self._directory
 
 
@@ -1011,6 +1040,7 @@
         sys.exit(1)
     def makeService(store):
         from twistedcaldav.config import config
+        config.TransactionTimeoutSeconds = 0
         return CalVerifyService(store, options, output, reactor, config)
     utilityMain(options['config'], makeService, reactor)
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/notifications.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/notifications.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/notifications.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -156,7 +156,7 @@
 
     pubsubNS = 'http://jabber.org/protocol/pubsub'
 
-    def __init__(self, jid, password, service, nodes, verbose):
+    def __init__(self, jid, password, service, nodes, verbose, sigint=True):
         resource = "pushmonitor.%s" % (uuid.uuid4().hex,)
         self.jid = "%s/%s" % (jid, resource)
         self.service = service
@@ -184,15 +184,20 @@
         self.addBootstrap(IQAuthInitializer.AUTH_FAILED_EVENT,
             self.authFailed)
 
-        signal.signal(signal.SIGINT, self.sigint_handler)
+        if sigint:
+            signal.signal(signal.SIGINT, self.sigint_handler)
 
     @inlineCallbacks
     def sigint_handler(self, num, frame):
         print " Shutting down..."
+        yield self.unsubscribeAll()
+        reactor.stop()
+
+    @inlineCallbacks
+    def unsubscribeAll(self):
         if self.xmlStream is not None:
             for node, (url, name, kind) in self.nodes.iteritems():
                 yield self.unsubscribe(node, name, kind)
-        reactor.stop()
 
     def connected(self, xmlStream):
         self.xmlStream = xmlStream

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/cmd.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/cmd.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/cmd.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -18,6 +18,13 @@
 Data store commands.
 """
 
+__all__ = [
+    "UsageError",
+    "UnknownArguments",
+    "CommandsBase",
+    "Commands",
+]
+
 #from twisted.python import log
 from twisted.internet.defer import succeed
 from twisted.internet.defer import inlineCallbacks, returnValue
@@ -27,8 +34,10 @@
 from txdav.common.icommondatastore import NotFoundError
 
 from calendarserver.tools.tables import Table
-from calendarserver.tools.shell.vfs import Folder
+from calendarserver.tools.shell.vfs import Folder, RootFolder
+from calendarserver.tools.shell.directory import findRecords, summarizeRecords, recordInfo
 
+
 class UsageError(Exception):
     """
     Usage error.
@@ -45,9 +54,15 @@
 
 
 class CommandsBase(object):
-    def __init__(self, wd):
-        self.wd = wd
+    def __init__(self, protocol):
+        self.protocol = protocol
 
+        self.wd = RootFolder(protocol.service)
+
+    @property
+    def terminal(self):
+        return self.protocol.terminal
+
     #
     # Utilities
     #
@@ -60,6 +75,10 @@
 
     @inlineCallbacks
     def getTargets(self, tokens):
+        """
+        For each given C{token}, locate a File to operate on.
+        @return: iterable of File objects.
+        """
         if tokens:
             result = []
             for token in tokens:
@@ -68,31 +87,66 @@
         else:
             returnValue((self.wd,))
 
-    def commands(self):
+    def commands(self, showHidden=False):
+        """
+        @return: an iterable of C{(name, method)} tuples, where
+        C{name} is the name of the command and C{method} is the method
+        that implements it.
+        """
         for attr in dir(self):
             if attr.startswith("cmd_"):
                 m = getattr(self, attr)
-                if not hasattr(m, "hidden"):
+                if showHidden or not hasattr(m, "hidden"):
                     yield (attr[4:], m)
 
     @staticmethod
     def complete(word, items):
+        """
+        List completions for the given C{word} from the given
+        C{items}.
+
+        Completions are the remaining portions of words in C{items}
+        that start with C{word}.
+
+        For example, if C{"foobar"} and C{"foo"} are in C{items}, then
+        C{""} and C{"bar"} are completions when C{word} C{"foo"}.
+
+        @return: an iterable of completions.
+        """
         for item in items:
             if item.startswith(word):
                 yield item[len(word):]
 
     def complete_commands(self, word):
-        return self.complete(word, (name for name, method in self.commands()))
+        """
+        @return: an iterable of command name completions.
+        """
+        def complete(showHidden):
+            return self.complete(
+                word,
+                (name for name, method in self.commands(showHidden=showHidden))
+            )
 
+        completions = tuple(complete(False))
+
+        # If no completions are found, try hidden commands.
+        if not completions:
+            completions = complete(True)
+
+        return completions
+
     @inlineCallbacks
     def complete_files(self, tokens, filter=None):
+        """
+        @return: an iterable of C{File} path completions.
+        """
         if filter is None:
-            filter = lambda items: True
+            filter = lambda item: True
 
         files = (
-            self.listEntryToString(item)
-            for item in (yield self.wd.list())
-            if filter(item)
+            entry.toString()
+            for entry in (yield self.wd.list())
+            if filter(entry)
         )
 
         if len(tokens) == 0:
@@ -102,22 +156,28 @@
         else:
             returnValue(())
 
-    @staticmethod
-    def listEntryToString(entry):
-        klass = entry[0]
-        name  = entry[1]
 
-        if issubclass(klass, Folder):
-            return "%s/" % (name,)
-        else:
-            return name
-
-
 class Commands(CommandsBase):
     """
     Data store commands.
     """
 
+    #
+    # Basic CLI tools
+    #
+
+    def cmd_exit(self, tokens):
+        """
+        Exit the shell.
+
+        usage: exit
+        """
+        if tokens:
+            raise UnknownArguments(tokens)
+
+        self.protocol.exit()
+
+
     def cmd_help(self, tokens):
         """
         Show help.
@@ -199,8 +259,8 @@
         usage: emulate editor
         """
         if not tokens:
-            if self.emulate:
-                self.terminal.write("Emulating %s.\n" % (self.emulate,))
+            if self.protocol.emulate:
+                self.terminal.write("Emulating %s.\n" % (self.protocol.emulate,))
             else:
                 self.terminal.write("Emulation disabled.\n")
             return
@@ -213,26 +273,62 @@
         if editor == "none":
             self.terminal.write("Disabling emulation.\n")
             editor = None
-        elif editor in self.emulation_modes:
+        elif editor in self.protocol.emulation_modes:
             self.terminal.write("Emulating %s.\n" % (editor,))
         else:
             raise UsageError("Unknown editor: %s" % (editor,))
 
-        self.emulate = editor
+        self.protocol.emulate = editor
 
         # FIXME: Need to update key registrations
 
-    cmd_emulate.hidden = "Incomplete"
+    cmd_emulate.hidden = "incomplete"
 
     def complete_emulate(self, tokens):
         if len(tokens) == 0:
-            return self.emulation_modes
+            return self.protocol.emulation_modes
         elif len(tokens) == 1:
-            return self.complete(tokens[0], self.emulation_modes)
+            return self.complete(tokens[0], self.protocol.emulation_modes)
         else:
             return ()
 
 
+    def cmd_log(self, tokens):
+        """
+        Enable logging.
+
+        usage: log [file]
+        """
+        if hasattr(self, "_logFile"):
+            self.terminal.write("Already logging to file: %s\n" % (self._logFile,))
+            return
+
+        if tokens:
+            fileName = tokens.pop(0)
+        else:
+            fileName = "/tmp/shell.log"
+
+        if tokens:
+            raise UnknownArguments(tokens)
+
+        from twisted.python.log import startLogging
+        try:
+            f = open(fileName, "w")
+        except (IOError, OSError), e:
+            self.terminal.write("Unable to open file %s: %s\n" % (fileName, e))
+            return
+
+        startLogging(f)
+
+        self._logFile = fileName
+
+    cmd_log.hidden = "debug tool"
+
+
+    #
+    # Filesystem tools
+    #
+
     def cmd_pwd(self, tokens):
         """
         Print working folder.
@@ -252,11 +348,11 @@
 
         usage: cd [folder]
         """
-        if not tokens:
+        if tokens:
+            dirname = tokens.pop(0)
+        else:
             return
 
-        dirname = tokens.pop(0)
-
         if tokens:
             raise UnknownArguments(tokens)
 
@@ -273,7 +369,7 @@
     def complete_cd(self, tokens):
         returnValue((yield self.complete_files(
             tokens,
-            filter = lambda item: issubclass(item[0], Folder)
+            filter = lambda item: True #issubclass(item[0], Folder)
         )))
 
 
@@ -288,14 +384,14 @@
         multiple = len(targets) > 0
 
         for target in targets:
-            rows = (yield target.list())
+            entries = (yield target.list())
             #
             # FIXME: this can be ugly if, for example, there are zillions
             # of entries to output. Paging would be good.
             #
             table = Table()
-            for row in rows:
-                table.addRow((self.listEntryToString(row),) + tuple(row[2:]))
+            for entry in entries:
+                table.addRow(entry.toFields())
 
             if multiple:
                 self.terminal.write("%s:\n" % (target,))
@@ -340,18 +436,63 @@
     complete_cat = CommandsBase.complete_files
 
 
-    def cmd_exit(self, tokens):
+    #
+    # Principal tools
+    #
+
+    @inlineCallbacks
+    def cmd_find_principals(self, tokens):
         """
-        Exit the shell.
+        Search for matching principals
 
-        usage: exit
+        usage: find_principal term
         """
+        if not tokens:
+            raise UsageError("No search term")
+
+        directory = self.protocol.service.directory
+
+        records = (yield findRecords(directory, tokens))
+
+        if records:
+            self.terminal.write((yield summarizeRecords(directory, records)))
+        else:
+            self.terminal.write("No matching principals found.")
+
+        self.terminal.nextLine()
+
+
+    @inlineCallbacks
+    def cmd_print_principal(self, tokens):
+        """
+        Print information about a principal
+
+        usage: print_principal uid
+        """
         if tokens:
+            uid = tokens.pop(0)
+        else:
+            raise UsageError("UID required")
+
+        if tokens:
             raise UnknownArguments(tokens)
 
-        self.exit()
+        directory = self.protocol.service.directory
 
+        record = directory.recordWithUID(uid)
 
+        if record:
+            self.terminal.write((yield recordInfo(directory, record)))
+        else:
+            self.terminal.write("No such principal.")
+
+        self.terminal.nextLine()
+
+
+    #
+    # Python prompt, for the win
+    #
+
     def cmd_python(self, tokens):
         """
         Switch to a python prompt.
@@ -368,7 +509,7 @@
 
             localVariables = dict(
                 self   = self,
-                store  = self.service.store,
+                store  = self.protocol.service.store,
                 schema = schema,
             )
 
@@ -377,42 +518,50 @@
                 if not key.startswith("_"):
                     localVariables[key] = value
 
-            self._interpreter = ManholeInterpreter(self, localVariables)
+            class Handler(object):
+                def addOutput(innerSelf, bytes, async=False):
+                    """
+                    This is a delegate method, called by ManholeInterpreter.
+                    """
+                    if async:
+                        self.terminal.write("... interrupted for Deferred ...\n")
+                    self.terminal.write(bytes)
+                    if async:
+                        self.terminal.write("\n")
+                        self.protocol.drawInputLine()
 
+            self._interpreter = ManholeInterpreter(Handler(), localVariables)
+
         def evalSomePython(line):
             if line == "exit":
                 # Return to normal command mode.
-                del self.lineReceived
-                del self.ps
-                del self.pn
-                self.drawInputLine()
+                del self.protocol.lineReceived
+                del self.protocol.ps
+                try:
+                    del self.protocol.pn
+                except AttributeError:
+                    pass
+                self.protocol.drawInputLine()
                 return
 
             more = self._interpreter.push(line)
-            self.pn = bool(more)
+            self.protocol.pn = bool(more)
+
             lw = self.terminal.lastWrite
             if not (lw.endswith("\n") or lw.endswith("\x1bE")):
                 self.terminal.write("\n")
-            self.drawInputLine()
+            self.protocol.drawInputLine()
 
-        self.lineReceived = evalSomePython
-        self.ps = (">>> ", "... ")
+        self.protocol.lineReceived = evalSomePython
+        self.protocol.ps = (">>> ", "... ")
 
-    cmd_python.hidden = "Still experimental / untested."
+    cmd_python.hidden = "debug tool"
 
 
-    def addOutput(self, bytes, async=False):
-        """
-        This is a delegate method, called by ManholeInterpreter.
-        """
-        if async:
-            self.terminal.write("... interrupted for Deferred ...\n")
-        self.terminal.write(bytes)
-        if async:
-            self.terminal.write("\n")
-            self.drawInputLine()
+    #
+    # SQL prompt, for not as winning
+    #
 
-
     def cmd_sql(self, tokens):
         """
         Switch to an SQL prompt.
@@ -424,36 +573,14 @@
 
         raise NotImplementedError("")
 
-    cmd_sql.hidden = "Not implemented."
+    cmd_sql.hidden = "not implemented"
 
 
-    def cmd_log(self, tokens):
-        """
-        Enable logging.
+    #
+    # Test tools
+    #
 
-        usage: log [file]
-        """
-        if hasattr(self, "_logFile"):
-            self.terminal.write("Already logging to file: %s\n" % (self._logFile,))
-            return
+    def cmd_raise(self, tokens):
+        raise RuntimeError(" ".join(tokens))
 
-        if tokens:
-            fileName = tokens.pop(0)
-        else:
-            fileName = "/tmp/shell.log"
-
-        if tokens:
-            raise UnknownArguments(tokens)
-
-        from twisted.python.log import startLogging
-        try:
-            f = open(fileName, "w")
-        except (IOError, OSError), e:
-            self.terminal.write("Unable to open file %s: %s\n" % (fileName, e))
-            return
-
-        startLogging(f)
-
-        self._logFile = fileName
-
-    cmd_log.hidden = "Debug tool"
+    cmd_raise.hidden = "test tool"

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/directory.py (from rev 9105, CalendarServer/trunk/calendarserver/tools/shell/directory.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/directory.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/directory.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,205 @@
+##
+# Copyright (c) 2012 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.
+##
+
+"""
+Directory tools
+"""
+
+__all__ = [
+    "findRecords",
+    "recordInfo",
+    "recordBasicInfo",
+    "recordGroupMembershipInfo",
+    "recordProxyAccessInfo",
+]
+
+
+import operator
+
+from twisted.internet.defer import succeed
+from twisted.internet.defer import inlineCallbacks, returnValue
+
+from calendarserver.tools.tables import Table
+
+
+ at inlineCallbacks
+def findRecords(directory, terms):
+    for term in terms:
+        searchFieldNames = ("fullName", "firstName", "lastName", "emailAddresses")
+        searchFields = tuple(
+            (fieldName, term, True, "contains")
+            for fieldName in searchFieldNames
+        )
+
+    records = (yield directory.recordsMatchingFields(searchFields))
+
+    returnValue(sorted(tuple(records), key=operator.attrgetter("fullName")))
+
+
+ at inlineCallbacks
+def recordInfo(directory, record):
+    """
+    Complete record information.
+    """
+    info = []
+
+    def add(name, subInfo):
+        if subInfo:
+            info.append("%s:" % (name,))
+            info.append(subInfo)
+
+    add("Directory record" , (yield recordBasicInfo(directory, record)))
+    add("Group memberships", (yield recordGroupMembershipInfo(directory, record)))
+    add("Proxy access"     , (yield recordProxyAccessInfo(directory, record)))
+
+    returnValue("\n".join(info))
+        
+
+def recordBasicInfo(directory, record):
+    """
+    Basic information for a record.
+    """
+    table = Table()
+
+    def add(name, value):
+        if value:
+            table.addRow((name, value))
+
+    add("Service"    , record.service   )
+    add("Record Type", record.recordType)
+
+    for shortName in record.shortNames:
+        add("Short Name", shortName)
+
+    add("GUID"      , record.guid     )
+    add("Full Name" , record.fullName )
+    add("First Name", record.firstName)
+    add("Last Name" , record.lastName )
+
+    for email in record.emailAddresses:
+        add("Email Address", email)
+
+    for cua in record.calendarUserAddresses:
+        add("Calendar User Address", cua)
+
+    add("Server ID"           , record.serverID              )
+    add("Partition ID"        , record.partitionID           )
+    add("Enabled"             , record.enabled               )
+    add("Enabled for Calendar", record.enabledForCalendaring )
+    add("Enabled for Contacts", record.enabledForAddressBooks)
+
+    return succeed(table.toString())
+
+
+def recordGroupMembershipInfo(directory, record):
+    """
+    Group membership info for a record.
+    """
+    rows = []
+
+    for group in record.groups():
+        rows.append((group.uid, group.shortNames[0], group.fullName))
+
+    if not rows:
+        return succeed(None)
+
+    rows = sorted(rows,
+        key = lambda row: (row[1], row[2])
+    )
+
+    table = Table()
+    table.addHeader(("UID", "Short Name", "Full Name"))
+    for row in rows:
+        table.addRow(row)
+
+    return succeed(table.toString())
+
+
+ at inlineCallbacks
+def recordProxyAccessInfo(directory, record):
+    """
+    Group membership info for a record.
+    """
+    # FIXME: This proxy finding logic should be in DirectoryRecord.
+
+    def meAndMyGroups(record=record, groups=set((record,))):
+        for group in record.groups():
+            groups.add(group)
+            meAndMyGroups(group, groups)
+        return groups
+
+    # FIXME: This module global is really gross.
+    from twistedcaldav.directory.calendaruserproxy import ProxyDBService
+
+    rows = []
+    proxyInfoSeen = set()
+    for record in meAndMyGroups():
+        proxyUIDs = (yield ProxyDBService.getMemberships(record.uid))
+
+        for proxyUID in proxyUIDs:
+            # These are of the form: F153A05B-FF27-4B6C-BD6D-D1239D0082B0#calendar-proxy-read
+            # I don't know how to get DirectoryRecord objects for the proxyUID here, so, let's cheat for now.
+            proxyUID, proxyType = proxyUID.split("#")
+            if (proxyUID, proxyType) not in proxyInfoSeen:
+                proxyRecord = directory.recordWithUID(proxyUID)
+                rows.append((proxyUID, proxyRecord.recordType, proxyRecord.shortNames[0], proxyRecord.fullName, proxyType))
+                proxyInfoSeen.add((proxyUID, proxyType))
+
+    if not rows:
+        returnValue(None)
+
+    rows = sorted(rows,
+        key = lambda row: (row[1], row[2], row[4])
+    )
+
+    table = Table()
+    table.addHeader(("UID", "Record Type", "Short Name", "Full Name", "Access"))
+    for row in rows:
+        table.addRow(row)
+
+    returnValue(table.toString())
+
+
+def summarizeRecords(directory, records):
+    table = Table()
+
+    table.addHeader((
+        "UID",
+        "Record Type",
+        "Short Names",
+        "Email Addresses",
+        "Full Name",
+    ))
+
+    def formatItems(items):
+        if items:
+            return ", ".join(items)
+        else:
+            return None
+
+    for record in records:
+        table.addRow((
+            record.uid,
+            record.recordType,
+            formatItems(record.shortNames),
+            formatItems(record.emailAddresses),
+            record.fullName,
+        ))
+
+    if table.rows:
+        return succeed(table.toString())
+    else:
+        return succeed(None)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/terminal.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/terminal.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/terminal.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -18,6 +18,15 @@
 Interactive shell for terminals.
 """
 
+__all__ = [
+    "usage",
+    "ShellOptions",
+    "ShellService",
+    "ShellProtocol",
+    "main",
+]
+
+
 import string
 import os
 import sys
@@ -42,7 +51,6 @@
 from calendarserver.tools.cmdline import utilityMain
 from calendarserver.tools.util import getDirectory
 from calendarserver.tools.shell.cmd import Commands, UsageError as CommandUsageError
-from calendarserver.tools.shell.vfs import Folder, RootFolder
 
 
 def usage(e=None):
@@ -114,7 +122,7 @@
         os.write(self.terminalFD, "\r\x1bc\r")
 
 
-class ShellProtocol(ReceiveLineProtocol, Commands):
+class ShellProtocol(ReceiveLineProtocol):
     """
     Data store shell protocol.
     """
@@ -127,11 +135,11 @@
 
     emulation_modes = ("emacs", "none")
 
-    def __init__(self, service):
+    def __init__(self, service, commandsClass=Commands):
         ReceiveLineProtocol.__init__(self)
-        Commands.__init__(self, RootFolder(service))
         self.service = service
         self.inputLines = []
+        self.commands = commandsClass(self)
         self.activeCommand = None
         self.emulate = "emacs"
 
@@ -157,6 +165,18 @@
             self.keyHandlers['\x01'] = self.handle_HOME   # Control-A
             self.keyHandlers['\x05'] = self.handle_END    # Control-E
 
+        def observer(event):
+            if not event["isError"]:
+                return
+
+            text = log.textFromEventDict(event)
+            if text is None:
+                return
+
+            self.service.reactor.callFromThread(self.terminal.write, text)
+
+        log.startLoggingWithObserver(observer)
+
     def handle_INT(self):
         """
         Handle ^C as an interrupt keystroke by resetting the current input
@@ -209,7 +229,7 @@
         if cmd and (tokens or word == ""):
             # Completing arguments
 
-            m = getattr(self, "complete_%s" % (cmd,), None)
+            m = getattr(self.commands, "complete_%s" % (cmd,), None)
             if not m:
                 return
             completions = tuple((yield m(tokens)))
@@ -217,7 +237,7 @@
             log.msg("COMPLETIONS: %r" % (completions,))
         else:
             # Completing command name
-            completions = tuple(self._complete_commands(cmd))
+            completions = tuple(self.commands.complete_commands(cmd))
 
         if len(completions) == 1:
             for completion in completions:
@@ -240,16 +260,7 @@
         self.terminal.loseConnection()
         self.service.reactor.stop()
 
-    @staticmethod
-    def _listEntryToString(entry):
-        klass = entry[0]
-        name  = entry[1]
 
-        if issubclass(klass, Folder):
-            return "%s/" % (name,)
-        else:
-            return name
-
     #
     # Command dispatch
     #
@@ -265,7 +276,7 @@
             cmd = tokens.pop(0)
             #print "Arguments: %r" % (tokens,)
 
-            m = getattr(self, "cmd_%s" % (cmd,), None)
+            m = getattr(self.commands, "cmd_%s" % (cmd,), None)
             if m:
                 def handleUsageError(f):
                     f.trap(CommandUsageError)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_cmd.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_cmd.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_cmd.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -21,59 +21,66 @@
 from txdav.common.icommondatastore import NotFoundError
 
 from calendarserver.tools.shell.cmd import CommandsBase
-from calendarserver.tools.shell.vfs import RootFolder
+from calendarserver.tools.shell.terminal import ShellProtocol
 
 
 class TestCommandsBase(twisted.trial.unittest.TestCase):
+    def setUp(self):
+        self.protocol = ShellProtocol(None, commandsClass=CommandsBase)
+        self.commands = self.protocol.commands
 
     @inlineCallbacks
     def test_getTargetNone(self):
-        cmd = CommandsBase(RootFolder(None))
-        target = (yield cmd.getTarget([]))
-        self.assertEquals(target, cmd.wd)
+        target = (yield self.commands.getTarget([]))
+        self.assertEquals(target, self.commands.wd)
 
     def test_getTargetMissing(self):
-        cmd = CommandsBase(RootFolder(None))
-        self.assertFailure(cmd.getTarget(["/foo"]), NotFoundError)
+        self.assertFailure(self.commands.getTarget(["/foo"]), NotFoundError)
 
     @inlineCallbacks
     def test_getTargetOne(self):
-        cmd = CommandsBase(RootFolder(None))
-        target = (yield cmd.getTarget(["users"]))
-        match = (yield cmd.wd.locate(["users"]))
+        target = (yield self.commands.getTarget(["users"]))
+        match = (yield self.commands.wd.locate(["users"]))
         self.assertEquals(target, match)
 
     @inlineCallbacks
     def test_getTargetSome(self):
-        cmd = CommandsBase(RootFolder(None))
-        target = (yield cmd.getTarget(["users", "blah"]))
-        match = (yield cmd.wd.locate(["users"]))
+        target = (yield self.commands.getTarget(["users", "blah"]))
+        match = (yield self.commands.wd.locate(["users"]))
         self.assertEquals(target, match)
 
     def test_commandsNone(self):
-        cmd = CommandsBase(RootFolder(None))
-        commands = cmd.commands()
+        allCommands = self.commands.commands()
+        self.assertEquals(sorted(allCommands), [])
 
-        self.assertEquals(sorted(commands), [])
+        allCommands = self.commands.commands(showHidden=True)
+        self.assertEquals(sorted(allCommands), [])
 
     def test_commandsSome(self):
-        class SomeCommands(CommandsBase):
-            def cmd_a(self, tokens):
-                pass
-            def cmd_b(self, tokens):
-                pass
-            def cmd_hidden(self, tokens):
-                pass
-            cmd_hidden.hidden = "Hidden"
+        protocol = ShellProtocol(None, commandsClass=SomeCommands)
+        commands = protocol.commands
 
-        cmd = SomeCommands(RootFolder(None))
-        commands = cmd.commands()
+        allCommands = commands.commands()
 
         self.assertEquals(
-            sorted(commands),
-            [ ("a", cmd.cmd_a), ("b", cmd.cmd_b) ]
+            sorted(allCommands),
+            [
+                ("a", commands.cmd_a),
+                ("b", commands.cmd_b),
+            ]
         )
 
+        allCommands = commands.commands(showHidden=True)
+
+        self.assertEquals(
+            sorted(allCommands),
+            [
+                ("a", commands.cmd_a),
+                ("b", commands.cmd_b),
+                ("hidden", commands.cmd_hidden),
+            ]
+        )
+
     def test_complete(self):
         items = (
             "foo",
@@ -86,6 +93,7 @@
         def c(word):
             return sorted(CommandsBase.complete(word, items))
 
+        self.assertEquals(c(""       ), sorted(items))
         self.assertEquals(c("f"      ), ["oo", "oobar"])
         self.assertEquals(c("foo"    ), ["", "bar"])
         self.assertEquals(c("foobar" ), [""])
@@ -93,3 +101,42 @@
         self.assertEquals(c("baz"    ), [""])
         self.assertEquals(c("q"      ), ["uux"])
         self.assertEquals(c("xyzzy"  ), [])
+
+    def test_completeCommands(self):
+        protocol = ShellProtocol(None, commandsClass=SomeCommands)
+        commands = protocol.commands
+
+        def c(word):
+            return sorted(commands.complete_commands(word))
+
+        self.assertEquals(c("" ), ["a", "b"])
+        self.assertEquals(c("a"), [""])
+        self.assertEquals(c("h"), ["idden"])
+        self.assertEquals(c("f"), [])
+
+    def test_completeFiles(self):
+        protocol = ShellProtocol(None, commandsClass=SomeCommands)
+        commands = protocol.commands
+
+        def c(word):
+            return sorted(commands.complete_files(word))
+
+        raise NotImplementedError()
+
+    test_completeFiles.todo = "Not implemented."
+
+    def test_listEntryToString(self):
+        raise NotImplementedError()
+        self.assertEquals(CommandsBase.listEntryToString(file, "stuff"), "")
+
+    test_listEntryToString.todo = "Not implemented"
+
+
+class SomeCommands(CommandsBase):
+    def cmd_a(self, tokens):
+        pass
+    def cmd_b(self, tokens):
+        pass
+    def cmd_hidden(self, tokens):
+        pass
+    cmd_hidden.hidden = "Hidden"

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_vfs.py (from rev 9105, CalendarServer/trunk/calendarserver/tools/shell/test/test_vfs.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_vfs.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/test/test_vfs.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2012 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 twisted.trial.unittest 
+from twisted.internet.defer import succeed
+
+from calendarserver.tools.shell.vfs import ListEntry
+from calendarserver.tools.shell.vfs import File, Folder
+
+
+class TestListEntry(twisted.trial.unittest.TestCase):
+    def test_toString(self):
+        self.assertEquals(ListEntry(File  , "thingo"           ).toString(), "thingo" )
+        self.assertEquals(ListEntry(File  , "thingo", Foo="foo").toString(), "thingo" )
+        self.assertEquals(ListEntry(Folder, "thingo"           ).toString(), "thingo/")
+        self.assertEquals(ListEntry(Folder, "thingo", Foo="foo").toString(), "thingo/")
+
+    def test_fieldNamesImplicit(self):
+        # This test assumes File doesn't set list.fieldNames.
+        assert not hasattr(File.list, "fieldNames")
+
+        self.assertEquals(set(ListEntry(File, "thingo").fieldNames), set(("Name",)))
+
+    def test_fieldNamesExplicit(self):
+        def fieldNames(fileClass):
+            return ListEntry(fileClass, "thingo", Flavor="Coconut", Style="Hard")
+
+        # Full list
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ("Name", "Flavor")
+        self.assertEquals(fieldNames(MyFile).fieldNames, ("Name", "Flavor"))
+
+        # Full list, different order
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ("Flavor", "Name")
+        self.assertEquals(fieldNames(MyFile).fieldNames, ("Flavor", "Name"))
+
+        # Omits Name, which is implicitly added
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ("Flavor",)
+        self.assertEquals(fieldNames(MyFile).fieldNames, ("Name", "Flavor"))
+
+        # Emtpy
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ()
+        self.assertEquals(fieldNames(MyFile).fieldNames, ("Name",))
+
+    def test_toFieldsImplicit(self):
+        # This test assumes File doesn't set list.fieldNames.
+        assert not hasattr(File.list, "fieldNames")
+
+        # Name first, rest sorted by field name
+        self.assertEquals(
+            tuple(ListEntry(File, "thingo", Flavor="Coconut", Style="Hard").toFields()),
+            ("thingo", "Coconut", "Hard")
+        )
+
+    def test_toFieldsExplicit(self):
+        def fields(fileClass):
+            return tuple(ListEntry(fileClass, "thingo", Flavor="Coconut", Style="Hard").toFields())
+
+        # Full list
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ("Name", "Flavor")
+        self.assertEquals(fields(MyFile), ("thingo", "Coconut"))
+
+        # Full list, different order
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ("Flavor", "Name")
+        self.assertEquals(fields(MyFile), ("Coconut", "thingo"))
+
+        # Omits Name, which is implicitly added
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ("Flavor",)
+        self.assertEquals(fields(MyFile), ("thingo", "Coconut"))
+
+        # Emtpy
+        class MyFile(File):
+            def list(self): return succeed(())
+            list.fieldNames = ()
+        self.assertEquals(fields(MyFile), ("thingo",))

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/vfs.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/vfs.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/shell/vfs.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -18,7 +18,26 @@
 Virtual file system for data store objects.
 """
 
+__all__ = [
+    "File",
+    "Folder",
+    "RootFolder",
+    "UIDsFolder",
+    "RecordFolder",
+    "UsersFolder",
+    "LocationsFolder",
+    "ResourcesFolder",
+    "GroupsFolder",
+    "PrincipalHomeFolder",
+    "CalendarHomeFolder",
+    "CalendarFolder",
+    "CalendarObject",
+    "AddressBookHomeFolder",
+]
+
+
 from cStringIO import StringIO
+from time import strftime, localtime
 
 from twisted.python import log
 from twisted.internet.defer import succeed
@@ -29,8 +48,49 @@
 from twistedcaldav.ical import InvalidICalendarDataError
 
 from calendarserver.tools.tables import Table
+from calendarserver.tools.shell.directory import recordInfo
 
 
+class ListEntry(object):
+    """
+    Information about a C{File} as returned by C{File.list()}.
+    """
+    def __init__(self, Class, Name, **fields):
+        self.fileClass = Class
+        self.fileName  = Name
+        self.fields    = fields
+
+        fields["Name"] = Name
+
+    def __str__(self):
+        return self.toString()
+
+    def isFolder(self):
+        return issubclass(self.fileClass, Folder)
+
+    def toString(self):
+        if self.isFolder():
+            return "%s/" % (self.fileName,)
+        else:
+            return self.fileName
+
+    @property
+    def fieldNames(self):
+        if not hasattr(self, "_fieldNames"):
+            if hasattr(self.fileClass.list, "fieldNames"):
+                if "Name" in self.fileClass.list.fieldNames:
+                    self._fieldNames = tuple(self.fileClass.list.fieldNames)
+                else:
+                    self._fieldNames = ("Name",) + tuple(self.fileClass.list.fieldNames)
+            else:
+                self._fieldNames = ["Name"] + sorted(n for n in self.fields if n != "Name")
+
+        return self._fieldNames
+
+    def toFields(self):
+        return tuple(self.fields[fieldName] for fieldName in self.fieldNames)
+
+
 class File(object):
     """
     Object in virtual data hierarchy.
@@ -57,7 +117,9 @@
         return succeed("%s (%s)" % (self, self.__class__.__name__))
 
     def list(self):
-        return succeed((File, str(self)))
+        return succeed((
+            ListEntry(self.__class__, self.path[-1]),
+        ))
 
 
 class Folder(File):
@@ -78,6 +140,9 @@
 
     @inlineCallbacks
     def locate(self, path):
+        if path and path[-1] == "":
+            path.pop()
+
         if not path:
             returnValue(RootFolder(self.service))
 
@@ -117,9 +182,9 @@
     def list(self):
         result = set()
         for name in self._children:
-            result.add((self._children[name].__class__, name))
+            result.add(ListEntry(self._children[name].__class__, name))
         for name in self._childClasses:
-            result.add((self._childClasses[name], name))
+            result.add(ListEntry(self._childClasses[name], name))
         return succeed(result)
 
 
@@ -173,7 +238,7 @@
         # FIXME: Add directory info (eg. name) to listing
 
         for txn, home in (yield self.service.store.eachCalendarHome()):
-            result.add((PrincipalHomeFolder, home.uid()))
+            result.add(ListEntry(PrincipalHomeFolder, home.uid()))
 
         returnValue(result)
 
@@ -203,6 +268,7 @@
         result = set()
 
         # FIXME ...?
+        yield 1
 
         returnValue(result)
 
@@ -254,7 +320,7 @@
     @inlineCallbacks
     def _initChildren(self):
         if not hasattr(self, "_didInitChildren"):
-            txn  = self.service.store.newTransaction()
+            txn = self.service.store.newTransaction()
 
             if (
                 self.record is not None and
@@ -265,12 +331,27 @@
             else:
                 create = False
 
-            home = (yield txn.calendarHomeWithUID(self.uid, create=create))
+            # Try assuming it exists
+            home = (yield txn.calendarHomeWithUID(self.uid, create=False))
+
+            if home is None and create:
+                # Doesn't exist, so create it in a different
+                # transaction, to avoid having to commit the live
+                # transaction.
+                txnTemp = self.service.store.newTransaction()
+                home = (yield txnTemp.calendarHomeWithUID(self.uid, create=True))
+                (yield txnTemp.commit())
+
+                # Fetch the home again. This time we expect it to be there.
+                home = (yield txn.calendarHomeWithUID(self.uid, create=False))
+                assert home
+
             if home:
                 self._children["calendars"] = CalendarHomeFolder(
                     self.service,
                     self.path + ("calendars",),
                     home,
+                    self.record,
                 )
 
             if (
@@ -282,12 +363,25 @@
             else:
                 create = False
 
+            # Again, assume it exists
             home = (yield txn.addressbookHomeWithUID(self.uid))
+
+            if not home and create:
+                # Repeat the above dance.
+                txnTemp = self.service.store.newTransaction()
+                home = (yield txnTemp.addressbookHomeWithUID(self.uid, create=True))
+                (yield txnTemp.commit())
+
+                # Fetch the home again. This time we expect it to be there.
+                home = (yield txn.addressbookHomeWithUID(self.uid, create=False))
+                assert home
+
             if home:
                 self._children["addressbooks"] = AddressBookHomeFolder(
                     self.service,
                     self.path + ("addressbooks",),
                     home,
+                    self.record,
                 )
 
         self._didInitChildren = True
@@ -307,115 +401,19 @@
     def list(self):
         return Folder.list(self)
 
-    @inlineCallbacks
     def describe(self):
-        result = []
-        result.append("Principal home for UID: %s\n" % (self.uid,))
+        return recordInfo(self.service.directory, self.record)
 
-        if self.record is not None:
-            #
-            # Basic record info
-            #
 
-            rows = []
-
-            def add(name, value):
-                if value:
-                    rows.append((name, value))
-
-            add("Service"    , self.record.service   )
-            add("Record Type", self.record.recordType)
-
-            for shortName in self.record.shortNames:
-                add("Short Name", shortName)
-
-            add("GUID"      , self.record.guid     )
-            add("Full Name" , self.record.fullName )
-            add("First Name", self.record.firstName)
-            add("Last Name" , self.record.lastName )
-
-            for email in self.record.emailAddresses:
-                add("Email Address", email)
-
-            for cua in self.record.calendarUserAddresses:
-                add("Calendar User Address", cua)
-
-            add("Server ID"           , self.record.serverID              )
-            add("Partition ID"        , self.record.partitionID           )
-            add("Enabled"             , self.record.enabled               )
-            add("Enabled for Calendar", self.record.enabledForCalendaring )
-            add("Enabled for Contacts", self.record.enabledForAddressBooks)
-
-            if rows:
-                result.append("Directory Record:")
-                result.append(tableString(rows, header=("Name", "Value")))
-
-            #
-            # Group memberships
-            #
-            rows = []
-
-            for group in self.record.groups():
-                rows.append((group.uid, group.shortNames[0], group.fullName))
-
-            if rows:
-                def sortKey(row):
-                    return (row[1], row[2])
-                result.append("Group Memberships:")
-                result.append(tableString(
-                    sorted(rows, key=sortKey),
-                    header=("UID", "Short Name", "Full Name")
-                ))
-
-            #
-            # Proxy for...
-            #
-
-            # FIXME: This logic should be in the DirectoryRecord.
-
-            def meAndMyGroups(record=self.record, groups=set((self.record,))):
-                for group in record.groups():
-                    groups.add(group)
-                    meAndMyGroups(group, groups)
-                return groups
-                
-            # FIXME: This module global is really gross.
-            from twistedcaldav.directory.calendaruserproxy import ProxyDBService
-
-            rows = []
-            proxyInfoSeen = set()
-            for record in meAndMyGroups():
-                proxyUIDs = (yield ProxyDBService.getMemberships(record.uid))
-
-                for proxyUID in proxyUIDs:
-                    # These are of the form: F153A05B-FF27-4B6C-BD6D-D1239D0082B0#calendar-proxy-read
-                    # I don't know how to get DirectoryRecord objects for the proxyUID here, so, let's cheat for now.
-                    proxyUID, proxyType = proxyUID.split("#")
-                    if (proxyUID, proxyType) not in proxyInfoSeen:
-                        proxyRecord = self.service.directory.recordWithUID(proxyUID)
-                        rows.append((proxyUID, proxyRecord.recordType, proxyRecord.shortNames[0], proxyRecord.fullName, proxyType))
-                        proxyInfoSeen.add((proxyUID, proxyType))
-
-            if rows:
-                def sortKey(row):
-                    return (row[1], row[2], row[4])
-                result.append("Proxy Access:")
-                result.append(tableString(
-                    sorted(rows, key=sortKey),
-                    header=("UID", "Record Type", "Short Name", "Full Name", "Access")
-                ))
-
-        returnValue("\n".join(result))
-
-
 class CalendarHomeFolder(Folder):
     """
     Calendar home folder.
     """
-    def __init__(self, service, path, home):
+    def __init__(self, service, path, home, record):
         Folder.__init__(self, service, path)
 
-        self.home = home
+        self.home   = home
+        self.record = record
 
     @inlineCallbacks
     def child(self, name):
@@ -428,34 +426,29 @@
     @inlineCallbacks
     def list(self):
         calendars = (yield self.home.calendars())
-        returnValue(((CalendarFolder, c.name()) for c in calendars))
+        returnValue((ListEntry(CalendarFolder, c.name()) for c in calendars))
 
     @inlineCallbacks
     def describe(self):
-        # created() -> int
-        # modified() -> int
-        # properties -> IPropertyStore
+        description = ["Calendar home:\n"]
 
+        #
+        # Attributes
+        #
         uid          = (yield self.home.uid())
         created      = (yield self.home.created())
         modified     = (yield self.home.modified())
         quotaUsed    = (yield self.home.quotaUsedBytes())
         quotaAllowed = (yield self.home.quotaAllowedBytes())
-        properties   = (yield self.home.properties())
 
-        result = []
-        result.append("Calendar home for UID: %s\n" % (uid,))
+        recordType      = (yield self.record.recordType)
+        recordShortName = (yield self.record.shortNames[0])
 
-        #
-        # Attributes
-        #
         rows = []
-        if created is not None:
-            # FIXME: convert to formatted string
-            rows.append(("Created", str(created)))
-        if modified is not None:
-            # FIXME: convert to formatted string
-            rows.append(("Last modified", str(modified)))
+        rows.append(("UID", uid))
+        rows.append(("Owner", "(%s)%s" % (recordType, recordShortName)))
+        rows.append(("Created"      , timeString(created)))
+        rows.append(("Last modified", timeString(modified)))
         if quotaUsed is not None:
             rows.append((
                 "Quota",
@@ -463,21 +456,17 @@
                 % (quotaUsed, quotaAllowed, quotaUsed / quotaAllowed)
             ))
 
-        if len(rows):
-            result.append("Attributes:")
-            result.append(tableString(rows, header=("Name", "Value")))
+        description.append("Attributes:")
+        description.append(tableString(rows))
 
         #
         # Properties
         #
+        properties = (yield self.home.properties())
         if properties:
-            result.append("Properties:")
-            result.append(tableString(
-                ((name, properties[name]) for name in sorted(properties)),
-                header=("Name", "Value")
-            ))
+            description.append(tableStringForProperties(properties))
 
-        returnValue("\n".join(result))
+        returnValue("\n".join(description))
 
 
 class CalendarFolder(Folder):
@@ -515,7 +504,33 @@
 
         returnValue(result)
 
+    @inlineCallbacks
+    def describe(self):
+        description = ["Calendar:\n"]
 
+        #
+        # Attributes
+        #
+        ownerHome = (yield self.calendar.ownerCalendarHome()) # FIXME: Translate into human
+        syncToken = (yield self.calendar.syncToken())
+
+        rows = []
+        rows.append(("Owner"     , ownerHome))
+        rows.append(("Sync Token", syncToken))
+
+        description.append("Attributes:")
+        description.append(tableString(rows))
+
+        #
+        # Properties
+        #
+        properties = (yield self.calendar.properties())
+        if properties:
+            description.append(tableStringForProperties(properties))
+
+        returnValue("\n".join(description))
+
+
 class CalendarObject(File):
     """
     Calendar object.
@@ -552,8 +567,13 @@
     @inlineCallbacks
     def list(self):
         (yield self.lookup())
-        returnValue(((CalendarObject, self.uid, self.componentType, self.summary.replace("\n", " ")),))
+        returnValue((ListEntry(CalendarObject, self.uid, {
+            "Component Type": self.componentType,
+            "Summary": self.summary.replace("\n", " "),
+        }),))
 
+    list.fieldNames = ("Component Type", "Summary")
+
     @inlineCallbacks
     def text(self):
         (yield self.lookup())
@@ -563,6 +583,11 @@
     def describe(self):
         (yield self.lookup())
 
+        description = ["Calendar object:\n"]
+
+        #
+        # Calendar object attributes
+        #
         rows = []
 
         rows.append(("UID", self.uid))
@@ -579,21 +604,51 @@
 
             rows.append(("Organizer", "%s%s%s" % (organizer.value(), name, email)))
 
+        rows.append(("Created" , timeString(self.object.created())))
+        rows.append(("Modified", timeString(self.object.modified())))
+
+        description.append("Attributes:")
+        description.append(tableString(rows))
+
         #
         # Attachments
         #
-#       attachments = (yield self.object.attachments())
-#       log.msg("%r" % (attachments,))
-#       for attachment in attachments:
-#           log.msg("%r" % (attachment,))
-#           # FIXME: Not getting any results here
+        attachments = (yield self.object.attachments())
+        for attachment in attachments:
+            contentType = attachment.contentType()
+            contentType = "%s/%s" % (contentType.mediaType, contentType.mediaSubtype)
 
-        returnValue("Calendar object:\n%s" % tableString(rows))
+            rows = []
+            rows.append(("Name"        , attachment.name()))
+            rows.append(("Size"        , "%s bytes" % (attachment.size(),)))
+            rows.append(("Content Type", contentType))
+            rows.append(("MD5 Sum"     , attachment.md5()))
+            rows.append(("Created"     , timeString(attachment.created())))
+            rows.append(("Modified"    , timeString(attachment.modified())))
 
+            description.append("Attachment:")
+            description.append(tableString(rows))
+
+        #
+        # Properties
+        #
+        properties = (yield self.object.properties())
+        if properties:
+            description.append(tableStringForProperties(properties))
+
+        returnValue("\n".join(description))
+
+
 class AddressBookHomeFolder(Folder):
     """
     Address book home folder.
     """
+    def __init__(self, service, path, home, record):
+        Folder.__init__(self, service, path)
+
+        self.home   = home
+        self.record = record
+
     # FIXME
 
 
@@ -607,3 +662,27 @@
     output = StringIO()
     table.printTable(os=output)
     return output.getvalue()
+
+
+def tableStringForProperties(properties):
+    return "Properties:\n%s" % (tableString((
+        (name.toString(), truncateAtNewline(properties[name]))
+        for name in sorted(properties)
+    )))
+
+
+def timeString(time):
+    if time is None:
+        return "(unknown)"
+
+    return strftime("%a, %d %b %Y %H:%M:%S %z(%Z)", localtime(time))
+
+
+def truncateAtNewline(text):
+    text = str(text)
+    try:
+        index = text.index("\n")
+    except ValueError:
+        return text
+
+    return text[:index] + "..."

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/tables.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/tables.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/tables.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -14,9 +14,20 @@
 # limitations under the License.
 ##
 
+"""
+Tables for fixed-width text display.
+"""
+
+__all__ = [
+    "Table",
+]
+
+
 from sys import stdout
 import types
+from cStringIO import StringIO
 
+
 class Table(object):
     """
     Class that allows pretty printing ascii tables.
@@ -109,6 +120,12 @@
         
         self.rows.append((None, skipColumns,))
 
+    def toString(self):
+
+        output = StringIO()
+        self.printTable(os=output)
+        return output.getvalue()
+
     def printTable(self, os=stdout):
         
         maxWidths = self._getMaxWidths()

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-Copyright (c) 2006-2010 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.
- -->
-
-<!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
-
-<accounts realm="/Search">
-  <user>
-    <uid>example1</uid>
-    <guid>D46F3D71-04B7-43C2-A7B6-6F92F92E61D0</guid>
-    <password>example</password>
-    <name>Example User1</name>
-    <email-address>example1 at example.com</email-address>
-  </user>
-  <user>
-    <uid>example2</uid>
-    <guid>47B16BB4-DB5F-4BF6-85FE-A7DA54230F92</guid>
-    <password>example</password>
-    <name>Example User2</name>
-    <email-address>example2 at example.com</email-address>
-  </user>
-  <user>
-    <uid>example3</uid>
-    <guid>AC478592-7783-44D1-B2AE-52359B4E8415</guid>
-    <password>example</password>
-    <name>Example User3</name>
-    <email-address>example3 at example.com</email-address>
-  </user>
-</accounts>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml (from rev 9105, CalendarServer/trunk/calendarserver/tools/test/calverify/accounts.xml)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/accounts.xml	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+Copyright (c) 2006-2010 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.
+ -->
+
+<!DOCTYPE accounts SYSTEM "../../../conf/auth/accounts.dtd">
+
+<accounts realm="/Search">
+  <user>
+    <uid>example1</uid>
+    <guid>D46F3D71-04B7-43C2-A7B6-6F92F92E61D0</guid>
+    <password>example</password>
+    <name>Example User1</name>
+    <email-address>example1 at example.com</email-address>
+  </user>
+  <user>
+    <uid>example2</uid>
+    <guid>47B16BB4-DB5F-4BF6-85FE-A7DA54230F92</guid>
+    <password>example</password>
+    <name>Example User2</name>
+    <email-address>example2 at example.com</email-address>
+  </user>
+  <user>
+    <uid>example3</uid>
+    <guid>AC478592-7783-44D1-B2AE-52359B4E8415</guid>
+    <password>example</password>
+    <name>Example User3</name>
+    <email-address>example3 at example.com</email-address>
+  </user>
+</accounts>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<accounts realm="/Search">
-</accounts>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml (from rev 9105, CalendarServer/trunk/calendarserver/tools/test/calverify/resources.xml)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/calverify/resources.xml	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<accounts realm="/Search">
+</accounts>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/deprovision/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/deprovision/caldavd.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/deprovision/caldavd.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -300,10 +300,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>conf/sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/gateway/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/gateway/caldavd.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/gateway/caldavd.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -300,10 +300,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>conf/sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/principals/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/principals/caldavd.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/principals/caldavd.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -300,10 +300,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>conf/sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/test_calverify.py (from rev 9105, CalendarServer/trunk/calendarserver/tools/test/test_calverify.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/test_calverify.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/calendarserver/tools/test/test_calverify.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,357 @@
+##
+# Copyright (c) 2012 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.
+##
+
+"""
+Tests for calendarserver.tools.calverify
+"""
+
+from StringIO import StringIO
+from calendarserver.tap.util import getRootResource
+from calendarserver.tools.calverify import CalVerifyService
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks
+from twisted.trial import unittest
+from twistedcaldav.config import config
+from txdav.caldav.datastore import util
+from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
+import os
+
+
+OK_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:OK
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Missing DTSTAMP
+BAD1_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD1
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Bad recurrence
+BAD2_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+RRULE:FREQ=DAILY;COUNT=3
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+RECURRENCE-ID:20000307T120000Z
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Bad recurrence
+BAD3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+RRULE:FREQ=DAILY;COUNT=3
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD2
+RECURRENCE-ID:20000307T120000Z
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# Missing Organizer
+BAD3_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD3
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+RRULE:FREQ=DAILY;COUNT=3
+SEQUENCE:2
+END:VEVENT
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD3
+RECURRENCE-ID:20000307T111500Z
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+SEQUENCE:2
+ORGANIZER:mailto:example2 at example.com
+ATTENDEE:mailto:example1 at example.com
+ATTENDEE:mailto:example2 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+# https Organizer
+BAD4_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD4
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example1 at example.com
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+# https Attendee
+BAD5_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD5
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:mailto:example1 at example.com
+ATTENDEE:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+# https Organizer and Attendee
+BAD6_ICS = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.1//EN
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+CREATED:20100303T181216Z
+UID:BAD6
+DTEND:20000307T151500Z
+TRANSP:OPAQUE
+SUMMARY:Ancient event
+DTSTART:20000307T111500Z
+DTSTAMP:20100303T181220Z
+ORGANIZER:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:http://demo.com:8008/principals/__uids__/D46F3D71-04B7-43C2-A7B6-6F92F92E61D0
+ATTENDEE:mailto:example2 at example.com
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
+
+class CalVerifyTests(CommonCommonTests, unittest.TestCase):
+    """
+    Tests for deleting events older than a given date
+    """
+
+    metadata = {
+        "accessMode": "PUBLIC",
+        "isScheduleObject": True,
+        "scheduleTag": "abc",
+        "scheduleEtags": (),
+        "hasPrivateComment": False,
+    }
+
+    requirements = {
+        "home1" : {
+            "calendar1" : {
+                "ok.ics" : (OK_ICS, metadata,),
+                "bad1.ics" : (BAD1_ICS, metadata,),
+                "bad2.ics" : (BAD2_ICS, metadata,),
+                "bad3.ics" : (BAD3_ICS, metadata,),
+                "bad4.ics" : (BAD4_ICS, metadata,),
+                "bad5.ics" : (BAD5_ICS, metadata,),
+                "bad6.ics" : (BAD6_ICS, metadata,),
+            }
+        },
+    }
+
+    @inlineCallbacks
+    def setUp(self):
+        yield super(CalVerifyTests, self).setUp()
+        self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
+        yield self.populate()
+
+        self.patch(config.DirectoryService.params, "xmlFile",
+            os.path.join(
+                os.path.dirname(__file__), "calverify", "accounts.xml"
+            )
+        )
+        self.patch(config.ResourceService.params, "xmlFile",
+            os.path.join(
+                os.path.dirname(__file__), "calverify", "resources.xml"
+            )
+        )
+        self.rootResource = getRootResource(config, self._sqlCalendarStore)
+        self.directory = self.rootResource.getDirectory()
+
+
+    @inlineCallbacks
+    def populate(self):
+        
+        # Need to bypass normal validation inside the store
+        util.validationBypass = True
+        yield populateCalendarsFrom(self.requirements, self.storeUnderTest(), migrating=True)
+        util.validationBypass = False
+        self.notifierFactory.reset()
+
+
+    def storeUnderTest(self):
+        """
+        Create and return a L{CalendarStore} for testing.
+        """
+        return self._sqlCalendarStore
+
+
+    def verifyResultsByUID(self, results, expected):
+        reported = set([(home, uid) for home, uid, _ignore_resid, _ignore_reason in results])
+        self.assertEqual(reported, expected)
+
+
+    @inlineCallbacks
+    def test_scanBadData(self):
+        """
+        CalVerifyService.doScan without fix. Make sure it detects common errors.
+        """
+
+        options = {
+            "ical":None,
+            "verbose":False,
+            "uuid":"",
+        }
+        output = StringIO()
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, False)
+
+        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD1",),
+            ("home1", "BAD2",),
+            ("home1", "BAD3",),
+            ("home1", "BAD4",),
+            ("home1", "BAD5",),
+            ("home1", "BAD6",),
+        )))
+
+
+    @inlineCallbacks
+    def test_fixBadData(self):
+        """
+        CalVerifyService.doScan without fix. Make sure it detects and fixes as much as it can.
+        """
+
+        options = {
+            "ical":None,
+            "verbose":False,
+            "uuid":"",
+        }
+        output = StringIO()
+        
+        # Do fix
+        self.patch(config.Scheduling.Options, "PrincipalHostAliases", "demo.com")
+        self.patch(config, "HTTPPort", 8008)
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, True)
+
+        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD1",),
+            ("home1", "BAD2",),
+            ("home1", "BAD3",),
+            ("home1", "BAD4",),
+            ("home1", "BAD5",),
+            ("home1", "BAD6",),
+        )))
+
+        # Do scan
+        calverify = CalVerifyService(self._sqlCalendarStore, options, output, reactor, config)
+        yield calverify.doScan(True, False, False)
+
+        self.assertEqual(calverify.results["Number of events to process"], 7)
+        self.verifyResultsByUID(calverify.results["Bad iCalendar data"], set((
+            ("home1", "BAD1",),
+        )))

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/caldavd-test.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -445,10 +445,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>
@@ -946,10 +942,21 @@
       	<!-- Optional extra logging for posgres -->
       	<!-- <string>-c log_lock_waits=TRUE</string> -->
       	<!-- <string>-c log_statement=all</string> -->
-      	<!-- <string>-c log_line_prefix='%p.%x '</string> -->
+      	<!-- <string>-c log_line_prefix='%t [%p]: [%l] '</string> -->
       </array>
     </dict>
 
+    <!-- SQL Query Caching -->
+    <key>QueryCaching</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>MemcachedPool</key>
+      <string>Default</string>
+      <key>ExpireSeconds</key>
+      <integer>3600</integer>
+    </dict>
+
     <!-- Group Membership Caching -->
     <key>GroupCaching</key>
     <dict>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-ldaptest.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-ldaptest.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-ldaptest.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -445,10 +445,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>
@@ -946,10 +942,21 @@
       	<!-- Optional extra logging for posgres -->
       	<!-- <string>-c log_lock_waits=TRUE</string> -->
       	<!-- <string>-c log_statement=all</string> -->
-      	<!-- <string>-c log_line_prefix='%p.%x '</string> -->
+      	<!-- <string>-c log_line_prefix='%t [%p]: [%l] '</string> -->
       </array>
     </dict>
 
+    <!-- SQL Query Caching -->
+    <key>QueryCaching</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>MemcachedPool</key>
+      <string>Default</string>
+      <key>ExpireSeconds</key>
+      <integer>3600</integer>
+    </dict>
+
     <!-- Group Membership Caching -->
     <key>GroupCaching</key>
     <dict>
@@ -998,7 +1005,7 @@
         Directory Address Book
       -->
     
-    <!--  Disable Directory Address Book-->
+    <!--  Disable Directory Address Book -->
     <!--
       <key>DirectoryAddressBook</key>
       <false/>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-odtest.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-odtest.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-odtest.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -445,10 +445,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>
@@ -946,10 +942,21 @@
       	<!-- Optional extra logging for posgres -->
       	<!-- <string>-c log_lock_waits=TRUE</string> -->
       	<!-- <string>-c log_statement=all</string> -->
-      	<!-- <string>-c log_line_prefix='%p.%x '</string> -->
+      	<!-- <string>-c log_line_prefix='%t [%p]: [%l] '</string> -->
       </array>
     </dict>
 
+    <!-- SQL Query Caching -->
+    <key>QueryCaching</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>MemcachedPool</key>
+      <string>Default</string>
+      <key>ExpireSeconds</key>
+      <integer>3600</integer>
+    </dict>
+
     <!-- Group Membership Caching -->
     <key>GroupCaching</key>
     <dict>
@@ -998,7 +1005,7 @@
         Directory Address Book
       -->
     
-    <!--  Disable Directory Address Book-->
+    <!--  Disable Directory Address Book -->
     <!--
       <key>DirectoryAddressBook</key>
       <false/>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-xmltest.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-xmltest.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/carddav-xmltest.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -445,10 +445,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>
@@ -946,10 +942,21 @@
       	<!-- Optional extra logging for posgres -->
       	<!-- <string>-c log_lock_waits=TRUE</string> -->
       	<!-- <string>-c log_statement=all</string> -->
-      	<!-- <string>-c log_line_prefix='%p.%x '</string> -->
+      	<!-- <string>-c log_line_prefix='%t [%p]: [%l] '</string> -->
       </array>
     </dict>
 
+    <!-- SQL Query Caching -->
+    <key>QueryCaching</key>
+    <dict>
+      <key>Enabled</key>
+      <true/>
+      <key>MemcachedPool</key>
+      <string>Default</string>
+      <key>ExpireSeconds</key>
+      <integer>3600</integer>
+    </dict>
+
     <!-- Group Membership Caching -->
     <key>GroupCaching</key>
     <dict>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/resources/caldavd-resources.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/resources/caldavd-resources.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/conf/resources/caldavd-resources.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -283,10 +283,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>conf/sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/certupdate/calendarcertupdate.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/certupdate/calendarcertupdate.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/certupdate/calendarcertupdate.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -9,7 +9,7 @@
 # For the replace command the handler returns
 # 0 = don't care/ cert replaced, 2 = an error occurred.
 #
-# Copyright (c) 2011 Apple Inc.  All Rights Reserved.
+# Copyright (c) 2011-2012 Apple Inc.  All Rights Reserved.
 #
 # IMPORTANT NOTE:  This file is licensed only for use on Apple-labeled
 # computers and is subject to the terms and conditions of the Apple

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.dist.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.dist.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.dist.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -31,10 +31,21 @@
 			<string>./python contrib/performance/loadtest/ampsim.py</string>
 			<string>./python contrib/performance/loadtest/ampsim.py</string>
 		</array>
+
 		<!-- Identify the server to be load tested. -->
 		<key>server</key>
 		<string>https://127.0.0.1:8443/</string>
 
+		<!-- Configure Admin Web UI. -->
+		<key>webadmin</key>
+		<dict>
+			<key>enabled</key>
+			<true/>
+			
+			<key>HTTPPort</key>
+			<integer>8080</integer>
+		</dict>
+
 		<!-- Define the credentials of the clients which will be used to load test 
 			the server. These credentials must already be valid on the server. -->
 		<key>accounts</key>
@@ -86,6 +97,11 @@
 				<!-- Number of seconds between the introduction of each group. -->
 				<key>interval</key>
 				<integer>3</integer>
+
+				<!-- Number of clients each user is assigned to. -->
+				<!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
+				<key>clientsPerUser</key>
+				<integer>1</integer>
 			</dict>
 
 		</dict>
@@ -100,19 +116,19 @@
 
 			<dict>
 
-				<!-- Here is a Snow Leopard iCal simulator. -->
+				<!-- Here is a OS X client simulator. -->
 				<key>software</key>
-				<string>contrib.performance.loadtest.ical.SnowLeopard</string>
+				<string>contrib.performance.loadtest.ical.OS_X_10_7</string>
 
-				<!-- Arguments to use to initialize the SnowLeopard instance. -->
+				<!-- Arguments to use to initialize the OS_X_10_7 instance. -->
 				<key>params</key>
 				<dict>
-					<!-- SnowLeopard can poll the calendar home at some interval. This is 
+					<!-- OS_X_10_7 can poll the calendar home at some interval. This is 
 						in seconds. -->
 					<key>calendarHomePollInterval</key>
 					<integer>30</integer>
 
-					<!-- If the server advertises xmpp push, SnowLeopard can wait for notifications 
+					<!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications 
 						about calendar home changes instead of polling for them periodically. If 
 						this option is true, then look for the server advertisement for xmpp push 
 						and use it if possible. Still fall back to polling if there is no xmpp push 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/config.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -23,6 +23,16 @@
 		<key>server</key>
 		<string>https://127.0.0.1:8443/</string>
 
+		<!-- Configure Admin Web UI. -->
+		<key>webadmin</key>
+		<dict>
+			<key>enabled</key>
+			<true/>
+			
+			<key>HTTPPort</key>
+			<integer>8080</integer>
+		</dict>
+
 		<!-- Define the credentials of the clients which will be used to load test 
 			the server. These credentials must already be valid on the server. -->
 		<key>accounts</key>
@@ -74,6 +84,11 @@
 				<!-- Number of seconds between the introduction of each group. -->
 				<key>interval</key>
 				<integer>3</integer>
+
+				<!-- Number of clients each user is assigned to. -->
+				<!-- Set weight of clients to 1 if this is > 1. Number of clients must match this value if > 1. -->
+				<key>clientsPerUser</key>
+				<integer>1</integer>
 			</dict>
 
 		</dict>
@@ -88,19 +103,19 @@
 
 			<dict>
 
-				<!-- Here is a Snow Leopard iCal simulator. -->
+				<!-- Here is a OS X client simulator. -->
 				<key>software</key>
-				<string>contrib.performance.loadtest.ical.SnowLeopard</string>
+				<string>contrib.performance.loadtest.ical.OS_X_10_7</string>
 
-				<!-- Arguments to use to initialize the SnowLeopard instance. -->
+				<!-- Arguments to use to initialize the OS_X_10_7 instance. -->
 				<key>params</key>
 				<dict>
-					<!-- SnowLeopard can poll the calendar home at some interval. This is 
+					<!-- OS_X_10_7 can poll the calendar home at some interval. This is 
 						in seconds. -->
 					<key>calendarHomePollInterval</key>
 					<integer>30</integer>
 
-					<!-- If the server advertises xmpp push, SnowLeopard can wait for notifications 
+					<!-- If the server advertises xmpp push, OS_X_10_7 can wait for notifications 
 						about calendar home changes instead of polling for them periodically. If 
 						this option is true, then look for the server advertisement for xmpp push 
 						and use it if possible. Still fall back to polling if there is no xmpp push 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/ical.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/ical.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2012 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.
@@ -24,13 +24,15 @@
 from pycalendar.duration import PyCalendarDuration
 from pycalendar.timezone import PyCalendarTimezone
 from pycalendar.datetime import PyCalendarDateTime
+from twext.web2.responsecode import MOVED_PERMANENTLY
+
 ElementTree.QName.__repr__ = lambda self: '<QName %r>' % (self.text,)
 
 from twisted.python.log import addObserver, err, msg
 from twisted.python.filepath import FilePath
-from twisted.python.failure import Failure
 from twisted.python.util import FancyEqMixin
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
+    succeed
 from twisted.internet.task import LoopingCall
 from twisted.web.http_headers import Headers
 from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT, PRECONDITION_FAILED
@@ -49,8 +51,8 @@
 from contrib.performance.loadtest.subscribe import Periodical
 
 
-def loadRequestBody(label):
-    return FilePath(__file__).sibling('request-data').child(label + '.request').getContent()
+def loadRequestBody(clientType, label):
+    return FilePath(__file__).sibling('request-data').child(clientType).child(label + '.request').getContent()
 
 
 SUPPORTED_REPORT_SET = '{DAV:}supported-report-set'
@@ -70,7 +72,12 @@
         self.response = response
 
 
+class MissingCalendarHome(Exception):
+    """
+    Raised when the calendar home for a user is 404
+    """
 
+
 class XMPPPush(object, FancyEqMixin):
     """
     This represents an XMPP PubSub location where push notifications for
@@ -104,48 +111,88 @@
 
 
 class Calendar(object):
-    def __init__(self, resourceType, componentTypes, name, url, ctag):
+    def __init__(self, resourceType, componentTypes, name, url, changeToken):
         self.resourceType = resourceType
         self.componentTypes = componentTypes
         self.name = name
         self.url = url
-        self.ctag = ctag
+        self.changeToken = changeToken
         self.events = {}
 
 
 
 class BaseClient(object):
-    user = None
-    _events = None
-    _calendars = None
+    """
+    Base interface for all simulated clients.
+    """
 
+    user = None         # User account details
+    _events = None      # Cache of events keyed by href
+    _calendars = None   # Cache of calendars keyed by href
+    started = False     # Whether or not startup() has been executed
+    _client_type = None # Type of this client used in logging 
+    _client_id = None   # Unique id for the client itself
+
+
     def _setEvent(self, href, event):
+        """
+        Cache the provided event
+        """
         self._events[href] = event
         calendar, uid = href.rsplit('/', 1)
         self._calendars[calendar + '/'].events[uid] = event
 
 
+    def _removeEvent(self, href):
+        """
+        Remove event from local cache.
+        """
+        del self._events[href]
+        calendar, uid = href.rsplit('/', 1)
+        del self._calendars[calendar + '/'].events[uid]
+
+
     def addEvent(self, href, vcalendar):
+        """
+        Called when a profile needs to add an event (no scheduling).
+        """
         raise NotImplementedError("%r does not implement addEvent" % (self.__class__,))
 
 
     def addInvite(self, href, vcalendar):
+        """
+        Called when a profile needs to add a new invite. The iCalendar data will already
+        contain ATTENDEEs.
+        """
         raise NotImplementedError("%r does not implement addInvite" % (self.__class__,))
 
 
     def deleteEvent(self, href):
+        """
+        Called when a profile needs to delete an event.
+        """
         raise NotImplementedError("%r does not implement deleteEvent" % (self.__class__,))
 
 
     def addEventAttendee(self, href, attendee):
+        """
+        Called when a profile needs to add an attendee to an existing event.
+        """
         raise NotImplementedError("%r does not implement addEventAttendee" % (self.__class__,))
 
 
     def changeEventAttendee(self, href, oldAttendee, newAttendee):
+        """
+        Called when a profile needs to change an attendee on an existing event.
+        Used when an attendee is accepting.
+        """
         raise NotImplementedError("%r does not implement changeEventAttendee" % (self.__class__,))
 
 
 class _PubSubClientFactory(PubSubClientFactory):
+    """
+    Factory for XMPP pubsub functionality.
+    """
     def __init__(self, client, *args, **kwargs):
         PubSubClientFactory.__init__(self, *args, **kwargs)
         self._client = client
@@ -163,49 +210,63 @@
             if node:
                 url, _ignore_name, _ignore_kind = self.nodes.get(node, (None, None, None))
                 if url is not None:
-                    self._client._checkCalendarsForEvents(url)
+                    self._client._checkCalendarsForEvents(url, push=True)
 
 
 
-class SnowLeopard(BaseClient):
+class BaseAppleClient(BaseClient):
     """
-    Implementation of the SnowLeopard iCal network behavior.
-
-    Anything SnowLeopard iCal does on its own, or any particular
-    network behaviors it takes in response to a user action, belong on
-    this class.
-
-    Usage-profile based behaviors ("the user modifies an event every
-    3.2 minutes") belong elsewhere.
+    Implementation of common OS X/iOS client behavior.
     """
 
-    USER_AGENT = "DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)"
+    USER_AGENT = None   # Override this for specific clients
 
     # The default interval, used if none is specified in external
-    # configuration.  This is also the actual value used by Snow
-    # Leopard iCal.
+    # configuration.
     CALENDAR_HOME_POLL_INTERVAL = 15 * 60
     
     # The maximum number of resources to retrieve in a single multiget
     MULTIGET_BATCH_SIZE = 200
 
-    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody('sl_startup_principal_propfind_initial')
-    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody('sl_startup_principal_propfind')
-    _STARTUP_PRINCIPALS_REPORT = loadRequestBody('sl_startup_principals_report')
-    _STARTUP_CALENDARHOME_PROPFIND = loadRequestBody('sl_startup_calendarhome_propfind')
-    _STARTUP_NOTIFICATION_PROPFIND = loadRequestBody('sl_startup_notification_propfind')
-    _STARTUP_PRINCIPAL_REPORT = loadRequestBody('sl_startup_principal_report')
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
 
-    _CALENDAR_PROPFIND = loadRequestBody('sl_calendar_propfind')
-    _CALENDAR_REPORT = loadRequestBody('sl_calendar_report')
-    _CALENDAR_REPORT_HREF = loadRequestBody('sl_calendar_report_href')
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = False
 
-    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody('sl_user_list_principal_property_search')
-    _POST_AVAILABILITY = loadRequestBody('sl_post_availability')
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
 
+    # Request body data
+    _LOAD_PATH = None
+
+    _STARTUP_WELL_KNOWN = None
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = None
+    _STARTUP_PRINCIPAL_PROPFIND = None
+    _STARTUP_PRINCIPALS_REPORT = None
+    _STARTUP_PRINCIPAL_EXPAND = None
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = None
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = None
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = None
+
+    _POLL_CALENDARHOME_PROPFIND = None
+    _POLL_CALENDAR_PROPFIND = None
+    _POLL_CALENDAR_PROPFIND_D1 = None
+    _POLL_CALENDAR_MULTIGET_REPORT = None
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = None
+    _POLL_CALENDAR_SYNC_REPORT = None
+    _POLL_NOTIFICATION_PROPFIND = None
+    _POLL_NOTIFICATION_PROPFIND_D1 = None
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = None
+    _POST_AVAILABILITY = None
+
     email = None
 
     def __init__(self, reactor, root, record, auth, calendarHomePollInterval=None, supportPush=True):
+        
+        self._client_id = str(uuid4())
+
         self.reactor = reactor
         self.agent = AuthHandlerAgent(Agent(self.reactor), auth)
         self.root = root
@@ -216,6 +277,8 @@
         self.calendarHomePollInterval = calendarHomePollInterval
 
         self.supportPush = supportPush
+        
+        self.supportSync = self._SYNC_REPORT
 
         # Keep track of the calendars on this account, keys are
         # Calendar URIs, values are Calendar instances.
@@ -235,46 +298,76 @@
         # values.
         self.xmpp = {}
 
+        # Keep track of push factories so we can unsubscribe at shutdown
+        self._pushFactories = []
+
         # Allow events to go out into the world.
         self.catalog = {
             "eventChanged": Periodical(),
             }
 
 
-    def _request(self, expectedResponseCodes, method, url, headers=None, body=None):
+    def _addDefaultHeaders(self, headers):
+        """
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        """
+        headers.setRawHeaders('User-Agent', [self.USER_AGENT])
+        
+    @inlineCallbacks
+    def _request(self, expectedResponseCodes, method, url, headers=None, body=None, method_label=None):
+        """
+        Execute a request and check against the expected response codes.
+        """
         if type(expectedResponseCodes) is int:
             expectedResponseCodes = (expectedResponseCodes,)
         if headers is None:
             headers = Headers({})
-        headers.setRawHeaders('User-Agent', [self.USER_AGENT])
-        msg(type="request", method=method, url=url, user=self.record.uid)
-        d = self.agent.request(method, url, headers, body)
+        self._addDefaultHeaders(headers)
+        msg(
+            type="request",
+            method=method_label if method_label else method,
+            url=url,
+            user=self.record.uid,
+            client_type=self._client_type,
+            client_id=self._client_id,
+        )
+
         before = self.reactor.seconds()
-        def report(response):
-            # XXX This is time to receive response headers, not time
-            # to receive full response.  Should measure the latter, if
-            # not both.
-            after = self.reactor.seconds()
+        response = yield self.agent.request(method, url, headers, body)
 
-            success = response.code in expectedResponseCodes
+        # XXX This is time to receive response headers, not time
+        # to receive full response.  Should measure the latter, if
+        # not both.
+        after = self.reactor.seconds()
 
-            # if not success:
-            #     import pdb; pdb.set_trace()
-            msg(
-                type="response", success=success, method=method,
-                headers=headers, body=body, code=response.code,
-                user=self.record.uid, duration=(after - before), url=url)
+        success = response.code in expectedResponseCodes
 
-            if success:
-                return response
-            raise IncorrectResponseCode(expectedResponseCodes, response)
-        d.addCallback(report)
-        return d
+        msg(
+            type="response",
+            success=success,
+            method=method_label if method_label else method,
+            headers=headers,
+            body=body,
+            code=response.code,
+            user=self.record.uid,
+            client_type=self._client_type,
+            client_id=self._client_id,
+            duration=(after - before),
+            url=url,
+        )
 
+        if success:
+            returnValue(response)
 
-    def _parseMultiStatus(self, response):
+        raise IncorrectResponseCode(expectedResponseCodes, response)
+
+
+    def _parseMultiStatus(self, response, otherTokens=False):
         """
-        Parse a <multistatus>
+        Parse a <multistatus> - might need to return other top-level elements
+        in the response - e.g. DAV:sync-token
         I{PROPFIND} request for the principal URL.
 
         @type response: C{str}
@@ -282,18 +375,160 @@
         """
         parser = PropFindParser()
         parser.parseData(response)
-        return parser.getResults()
+        if otherTokens:
+            return (parser.getResults(), parser.getOthers(),)
+        else:
+            return parser.getResults()
 
     
     _CALENDAR_TYPES = set([
             caldavxml.calendar,
             caldavxml.schedule_inbox,
-            caldavxml.schedule_outbox,
-            csxml.notification,
-            csxml.dropbox_home,
             ])
-    def _extractCalendars(self, response, calendarHome=None):
+
+    @inlineCallbacks
+    def _propfind(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), method_label=None):
         """
+        Issue a PROPFIND on the chosen URL
+        """
+        hdrs = Headers({'content-type': ['text/xml']})
+        if depth is not None:
+            hdrs.addRawHeader('depth', depth)
+        response = yield self._request(
+            allowedStatus,
+            'PROPFIND',
+            self.root + url[1:].encode('utf-8'),
+            hdrs,
+            StringProducer(body),
+            method_label=method_label,
+        )
+        body = yield readBody(response)
+        result = self._parseMultiStatus(body)
+        returnValue(result)
+
+
+    @inlineCallbacks
+    def _proppatch(self, url, body):
+        """
+        Issue a PROPPATCH on the chosen URL
+        """
+        hdrs = Headers({'content-type': ['text/xml']})
+        response = yield self._request(
+            (OK, MULTI_STATUS,),
+            'PROPPATCH',
+            self.root + url[1:].encode('utf-8'),
+            hdrs,
+            StringProducer(body)
+        )
+        if response.code == MULTI_STATUS:
+            body = yield readBody(response)
+            result = self._parseMultiStatus(body)
+            returnValue(result)
+        else:
+            returnValue(None)
+
+
+    @inlineCallbacks
+    def _report(self, url, body, depth='0', allowedStatus=(MULTI_STATUS,), otherTokens=False, method_label=None):
+        """
+        Issue a REPORT on the chosen URL
+        """
+        hdrs = Headers({'content-type': ['text/xml']})
+        if depth is not None:
+            hdrs.addRawHeader('depth', depth)
+        response = yield self._request(
+            allowedStatus,
+            'REPORT',
+            self.root + url[1:].encode('utf-8'),
+            hdrs,
+            StringProducer(body),
+            method_label=method_label,
+        )
+        body = yield readBody(response)
+        result = self._parseMultiStatus(body, otherTokens)
+        returnValue(result)
+
+
+    def _startupPropfindWellKnown(self):
+        """
+        Issue a PROPFIND on the /.well-known/caldav/ URL
+        """
+        return self._propfind(
+            '/.well-known/caldav/',
+            self._STARTUP_WELL_KNOWN,
+            allowedStatus=(MULTI_STATUS, MOVED_PERMANENTLY),
+        )
+
+
+    def _startupPropfindRoot(self):
+        """
+        Issue a PROPFIND on the / URL
+        """
+        return self._propfind(
+            '/',
+            self._STARTUP_WELL_KNOWN,
+        )
+
+
+    @inlineCallbacks
+    def _principalPropfindInitial(self, user):
+        """
+        Issue a PROPFIND on the /principals/users/<uid> URL to retrieve
+        the /principals/__uids__/<guid> principal URL
+        """
+        principalURL = '/principals/users/' + user + '/'
+        result = yield self._propfind(
+            '/principals/users/' + user + '/',
+            self._STARTUP_PRINCIPAL_PROPFIND_INITIAL,
+        )
+        returnValue(result[principalURL])
+
+
+    @inlineCallbacks
+    def _principalPropfind(self):
+        """
+        Issue a PROPFIND on the likely principal URL for the given
+        user and return a L{Principal} instance constructed from the
+        response.
+        """
+        result = yield self._propfind(
+            self.principalURL,
+            self._STARTUP_PRINCIPAL_PROPFIND,
+        )
+        returnValue(result[self.principalURL])
+
+
+    def _principalSearchPropertySetReport(self, principalCollectionSet):
+        """
+        Issue a principal-search-property-set REPORT against the chosen URL
+        """
+        return self._report(
+            principalCollectionSet,
+            self._STARTUP_PRINCIPALS_REPORT,
+            allowedStatus=(OK,),
+            method_label="REPORT{pset}",
+        )
+
+
+    @inlineCallbacks
+    def _calendarHomePropfind(self, calendarHomeSet):
+        """
+        Do the poll Depth:1 PROPFIND on the calendar home.
+        """
+        if not calendarHomeSet.endswith('/'):
+            calendarHomeSet = calendarHomeSet + '/'
+        result = yield self._propfind(
+            calendarHomeSet,
+            self._POLL_CALENDARHOME_PROPFIND,
+            depth='1',
+            method_label="PROPFIND{home}",
+        )
+        calendars = self._extractCalendars(result, calendarHomeSet)
+        returnValue((calendars, result,))
+
+
+    def _extractCalendars(self, results, calendarHome=None):
+        """
         Parse a calendar home PROPFIND response and create local state
         representing the calendars it contains.
 
@@ -301,14 +536,10 @@
         that from the response.
         """
         calendars = []
-        principals = self._parseMultiStatus(response)
+        for href in results:
 
-        # XXX Here, it would be really great to somehow use
-        # CalDAVClientLibrary.client.principal.CalDAVPrincipal.listCalendars
-        for principal in principals:
-
-            if principal == calendarHome:
-                text = principals[principal].getTextProperties()
+            if href == calendarHome:
+                text = results[href].getTextProperties()
                 try:
                     server = text[csxml.xmpp_server]
                     uri = text[csxml.xmpp_uri]
@@ -317,123 +548,81 @@
                     pass
                 else:
                     if server and uri:
-                        self.xmpp[principal] = XMPPPush(server, uri, pushkey)
+                        self.xmpp[href] = XMPPPush(server, uri, pushkey)
 
-            nodes = principals[principal].getNodeProperties()
+            nodes = results[href].getNodeProperties()
             for nodeType in nodes[davxml.resourcetype].getchildren():
                 if nodeType.tag in self._CALENDAR_TYPES:
-                    textProps = principals[principal].getTextProperties()
+                    textProps = results[href].getTextProperties()
                     componentTypes = set()
                     if nodeType.tag == caldavxml.calendar:
                         if caldavxml.supported_calendar_component_set in nodes:
                             for comp in nodes[caldavxml.supported_calendar_component_set].getchildren():
                                 componentTypes.add(comp.get("name").upper())
+                    
+                    changeTag = davxml.sync_token if self.supportSync else csxml.getctag
                     calendars.append(Calendar(
                             nodeType.tag,
                             componentTypes,
                             textProps.get(davxml.displayname, None),
-                            principal,
-                            textProps.get(csxml.getctag, None),
+                            href,
+                            textProps.get(changeTag, None),
                             ))
                     break
         return calendars
 
 
-    def _principalPropfindInitial(self, user):
+    def _updateCalendar(self, calendar, newToken):
         """
-        Issue a PROPFIND on the /principals/users/<uid> URL to retrieve
-        the /principals/__uids__/<guid> principal URL
+        Update the local cached data for a calendar in an appropriate manner.
         """
-        principalURL = '/principals/users/' + user + '/'
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + principalURL[1:].encode('utf-8'),
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPAL_PROPFIND_INITIAL))
-        d.addCallback(readBody)
-        d.addCallback(self._parseMultiStatus)
-        def get(result):
-            return result[principalURL]
-        d.addCallback(get)
-        return d
+        if self.supportSync:
+            return self._updateCalendar_SYNC(calendar, newToken)
+        else:
+            return self._updateCalendar_PROPFIND(calendar, newToken)
 
-
-    def _principalPropfind(self):
+    @inlineCallbacks
+    def _updateCalendar_PROPFIND(self, calendar, newToken):
         """
-        Issue a PROPFIND on the likely principal URL for the given
-        user and return a L{Principal} instance constructed from the
-        response.
+        Sync a collection by doing a full PROPFIND Depth:1 on it and then sync
+        the results with local cached data.
         """
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + self.principalURL[1:].encode('utf-8'),
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPAL_PROPFIND))
-        d.addCallback(readBody)
-        d.addCallback(self._parseMultiStatus)
-        def get(result):
-            return result[self.principalURL]
-        d.addCallback(get)
-        return d
 
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
 
-    def _principalsReport(self, principalCollectionSet):
-        if principalCollectionSet.startswith('/'):
-            principalCollectionSet = principalCollectionSet[1:]
-        d = self._request(
-            OK,
-            'REPORT',
-            self.root + principalCollectionSet,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPALS_REPORT))
-        d.addCallback(readBody)
-        return d
+        result = yield self._propfind(
+            calendar.url,
+            self._POLL_CALENDAR_PROPFIND_D1,
+            depth='1',
+            method_label="PROPFIND{calendar}"
+        )
 
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
 
-    def _calendarHomePropfind(self, calendarHomeSet):
-        if calendarHomeSet.startswith('/'):
-            calendarHomeSet = calendarHomeSet[1:]
-        if not calendarHomeSet.endswith('/'):
-            calendarHomeSet = calendarHomeSet + '/'
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + calendarHomeSet,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['1']}),
-            StringProducer(self._STARTUP_CALENDARHOME_PROPFIND))
-        d.addCallback(readBody)
-        d.addCallback(self._extractCalendars, '/' + calendarHomeSet)
-        return d
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
 
 
     @inlineCallbacks
-    def _updateCalendar(self, calendar):
-        url = calendar.url
-        if url.startswith('/'):
-            url = url[1:]
+    def _updateCalendar_SYNC(self, calendar, newToken):
+        """
+        Execute a sync REPORT against a calendar and apply changes to the local cache.
+        The new token from the changed collection is passed in and must be applied to
+        the existing calendar once sync is done.
+        """
 
-        # First do a PROPFIND on the calendar to learn about events it
-        # might have.
-        response = yield self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + url,
-            Headers({'content-type': ['text/xml'], 'depth': ['1']}),
-            StringProducer(self._CALENDAR_PROPFIND))
+        # Get changes from sync REPORT (including the other nodes at the top-level
+        # which will have the new sync token.
+        result, others = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_SYNC_REPORT % {'sync-token': calendar.changeToken},
+            depth='1',
+            otherTokens = True,
+            method_label="REPORT{sync}",
+        )
 
-        body = yield readBody(response)
-
-        result = self._parseMultiStatus(body)
         changed = []
         for responseHref in result:
             if responseHref == calendar.url:
@@ -445,23 +634,78 @@
                 # XXX Ignore things with no etag?  Seems to be dropbox.
                 continue
 
+            # Differentiate a remove vs new/update result
+            if result[responseHref].getStatus() / 100 == 2:
+                if responseHref not in self._events:
+                    self._setEvent(responseHref, Event(responseHref, None))
+                    
+                event = self._events[responseHref]
+                if event.etag != etag:
+                    changed.append(responseHref)
+            elif result[responseHref].getStatus() == 404:
+                self._removeEvent(responseHref)
+
+        yield self._updateChangedEvents(calendar, changed)
+
+        # Now update calendar to the new token taken from the report
+        for node in others:
+            if node.tag == davxml.sync_token:
+                newToken = node.text
+                break
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def _updateApplyChanges(self, calendar, multistatus, old_hrefs):
+        """
+        Given a multistatus for an entire collection, sync the reported items
+        against the cached items.
+        """
+        
+        # Detect changes and new items
+        all_hrefs = []
+        changed_hrefs = []
+        for responseHref in multistatus:
+            if responseHref == calendar.url:
+                continue
+            all_hrefs.append(responseHref)
+            try:
+                etag = multistatus[responseHref].getTextProperties()[davxml.getetag]
+            except KeyError:
+                # XXX Ignore things with no etag?  Seems to be dropbox.
+                continue
+
             if responseHref not in self._events:
                 self._setEvent(responseHref, Event(responseHref, None))
                 
             event = self._events[responseHref]
             if event.etag != etag:
-                changed.append(responseHref)
-            
+                changed_hrefs.append(responseHref)
+        
+        # Retrieve changes
+        yield self._updateChangedEvents(calendar, changed_hrefs)
+    
+        # Detect removed items and purge them
+        remove_hrefs = old_hrefs - set(all_hrefs)
+        for href in remove_hrefs:
+            self._removeEvent(href)
+
+        
+    @inlineCallbacks
+    def _updateChangedEvents(self, calendar, changed):
+        """
+        Given a set of changed hrefs, batch multiget them all to update the
+        local cache.
+        """
+
         while changed:
             batchedHrefs = changed[:self.MULTIGET_BATCH_SIZE]
             changed = changed[self.MULTIGET_BATCH_SIZE:]
     
-            response = yield self._eventReport(url, batchedHrefs)
-            body = yield readBody(response)
-            multistatus = self._parseMultiStatus(body)
+            multistatus = yield self._eventReport(calendar.url, batchedHrefs)
             for responseHref in batchedHrefs:
                 res = multistatus[responseHref]
-                if res.getStatus() is None or " 404 " not in res.getStatus():
+                if res.getStatus() == 200:
                     text = res.getTextProperties()
                     etag = text[davxml.getetag]
                     try:
@@ -484,116 +728,128 @@
     def _eventReport(self, calendar, events):
         # Next do a REPORT on events that might have information
         # we don't know about.
-        hrefs = "".join([self._CALENDAR_REPORT_HREF % {'href': event} for event in events])
-        return self._request(
-            MULTI_STATUS,
-            'REPORT',
-            self.root + calendar,
-            Headers({'content-type': ['text/xml']}),
-            StringProducer(self._CALENDAR_REPORT % {'hrefs': hrefs}))
+        hrefs = "".join([self._POLL_CALENDAR_MULTIGET_REPORT_HREF % {'href': event} for event in events])
+        return self._report(
+            calendar,
+            self._POLL_CALENDAR_MULTIGET_REPORT % {'hrefs': hrefs},
+            depth=None,
+            method_label="REPORT{multiget}",
+        )
 
 
-    def _checkCalendarsForEvents(self, calendarHomeSet):
+    @inlineCallbacks
+    def _checkCalendarsForEvents(self, calendarHomeSet, firstTime=False, push=False):
+        """
+        The actions a client does when polling for changes, or in response to a
+        push notification of a change. There are some actions done on the first poll
+        we should emulate.
+        """
+
+        result = True
+        try:
+            result = yield self._newOperation("push" if push else "poll", self._poll(calendarHomeSet, firstTime))
+        finally:
+            if result:
+                try:
+                    self._checking.remove(calendarHomeSet)
+                except KeyError:
+                    pass
+        returnValue(result)
+
+    @inlineCallbacks
+    def _poll(self, calendarHomeSet, firstTime):
         if calendarHomeSet in self._checking:
-            return
+            returnValue(False)
         self._checking.add(calendarHomeSet)
-        d = self._calendarHomePropfind(calendarHomeSet)
-        @inlineCallbacks
-        def cbCalendars(calendars):
-            for cal in calendars:
-                if cal.url not in self._calendars:
-                    # Calendar seen for the first time - reload it
-                    self._calendars[cal.url] = cal
-                    yield self._updateCalendar(cal)
-                elif self._calendars[cal.url].ctag != cal.ctag:
-                    # Calendar changed - update to new ctag and reload
-                    self._calendars[cal.url].ctag = cal.ctag
-                    yield self._updateCalendar(cal)
-        d.addCallback(cbCalendars)
-        d = self._newOperation("poll", d)
-        def ebCalendars(reason):
-            reason.trap(IncorrectResponseCode)
-        d.addErrback(ebCalendars)
-        def cleanupChecking(passthrough):
-            self._checking.remove(calendarHomeSet)
-            return passthrough
-        d.addBoth(cleanupChecking)
-        return d
 
+        calendars, results = yield self._calendarHomePropfind(calendarHomeSet)
+        
+        # First time operations
+        if firstTime:
+            yield self._pollFirstTime1(results[calendarHomeSet], calendars)
 
-    def _notificationPropfind(self, notificationURL):
-        if notificationURL.startswith('/'):
-            notificationURL = notificationURL[1:]
-        d = self._request(
-            MULTI_STATUS,
-            'PROPFIND',
-            self.root + notificationURL,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['1']}),
-            StringProducer(self._STARTUP_NOTIFICATION_PROPFIND))
-        d.addCallback(readBody)
-        d.addCallback(self._extractCalendars)
-        return d
+        # Normal poll
+        for cal in calendars:
+            newToken = cal.changeToken
+            if cal.url not in self._calendars:
+                # Calendar seen for the first time - reload it
+                self._calendars[cal.url] = cal
+                cal.changeToken = ""
+                yield self._updateCalendar(self._calendars[cal.url], newToken)
+            elif self._calendars[cal.url].changeToken != newToken:
+                # Calendar changed - reload it
+                yield self._updateCalendar(self._calendars[cal.url], newToken)
 
-    
-    def _principalReport(self, principalURL):
-        if principalURL.startswith('/'):
-            principalURL = principalURL[1:]
-        d = self._request(
-            OK,
-            'REPORT',
-            self.root + principalURL,
-            Headers({
-                    'content-type': ['text/xml'],
-                    'depth': ['0']}),
-            StringProducer(self._STARTUP_PRINCIPAL_REPORT))
-        d.addCallback(readBody)
-        return d
+        # When there is no sync REPORT, clients have to do a full PROPFIND
+        # on the notification collection because there is no ctag
+        if self.notificationURL is not None and not self.supportSync:
+            yield self._notificationPropfind(self.notificationURL)
+            yield self._notificationChangesPropfind(self.notificationURL)
 
+        # One time delegate expansion
+        if firstTime:
+            yield self._pollFirstTime2()
+            
+        returnValue(True)
 
     @inlineCallbacks
-    def startup(self):
+    def _pollFirstTime1(self, homeNode, calendars):
+        # Detect sync report if needed
+        if self.supportSync:
+            nodes = homeNode.getNodeProperties()
+            syncnodes = nodes[davxml.supported_report_set].findall(
+                str(davxml.supported_report) + "/" +
+                str(davxml.report) + "/" +
+                str(davxml.sync_collection)
+            )
+            self.supportSync = len(syncnodes) != 0
 
-        # PROPFIND /principals/users/<uid> to retrieve /principals/__uids__/<guid>
-        response = yield self._principalPropfindInitial(self.record.uid)
-        hrefs = response.getHrefProperties()
-        self.principalURL = hrefs[davxml.principal_URL].toString()
+        # Patch calendar properties
+        for cal in calendars:
+            if cal.name != "inbox":
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_COLOR)
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_ORDER)
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_TIMEZONE)
 
-        # Using the actual principal URL, retrieve principal information
-        principal = yield self._principalPropfind()
 
-        hrefs = principal.getHrefProperties()
+    def _pollFirstTime2(self):
+        return self._principalExpand(self.principalURL)
 
-        # Remember our outbox
-        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
 
-        # Remember our own email-like principal address
-        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
-            if principalURL.toString().startswith(u"mailto:"):
-                self.email = principalURL.toString()
-            elif principalURL.toString().startswith(u"urn:"):
-                self.uuid = principalURL.toString()
-        if self.email is None:
-            raise ValueError("Cannot operate without a mail-style principal URL")
+    @inlineCallbacks
+    def _notificationPropfind(self, notificationURL):
+        result = yield self._propfind(
+            notificationURL,
+            self._POLL_NOTIFICATION_PROPFIND,
+        )
+        returnValue(result)
 
-        # Do another kind of thing I guess
-        principalCollection = hrefs[davxml.principal_collection_set].toString()
-        (yield self._principalsReport(principalCollection))
+    
+    @inlineCallbacks
+    def _notificationChangesPropfind(self, notificationURL):
+        result = yield self._propfind(
+            notificationURL,
+            self._POLL_NOTIFICATION_PROPFIND_D1,
+            depth='1',
+        )
+        returnValue(result)
 
-        # Whatever
+    
+    @inlineCallbacks
+    def _principalExpand(self, principalURL):
+        result = yield self._report(
+            principalURL,
+            self._STARTUP_PRINCIPAL_EXPAND,
+            depth=None,
+            method_label="REPORT{expand}",
+        )
+        returnValue(result)
 
-        # Learn stuff I guess
-        # notificationURL = hrefs[csxml.notification_URL].toString()
-        # (yield self._notificationPropfind(notificationURL))
 
-        # More too
-        # principalURL = hrefs[davxml.principal_URL].toString()
-        # (yield self._principalReport(principalURL))
+    def startup(self):
+        raise NotImplementedError
 
-        returnValue(principal)
 
-
     def _calendarCheckLoop(self, calendarHome):
         """
         Periodically check the calendar home for changes to calendars.
@@ -603,19 +859,42 @@
         return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
 
 
+    @inlineCallbacks
     def _newOperation(self, label, deferred):
         before = self.reactor.seconds()
-        msg(type="operation", phase="start", user=self.record.uid, label=label)
-        def finished(passthrough):
-            success = not isinstance(passthrough, Failure)
-            if not success:
-                passthrough.trap(IncorrectResponseCode)
-            after = self.reactor.seconds()
-            msg(type="operation", phase="end", duration=after - before,
-                user=self.record.uid, label=label, success=success)
-            return passthrough
-        deferred.addBoth(finished)
-        return deferred
+        msg(
+            type="operation",
+            phase="start",
+            user=self.record.uid, 
+            client_type=self._client_type,
+            client_id=self._client_id,
+            label=label,
+        )
+        
+        try:
+            result = yield deferred
+        except IncorrectResponseCode:
+            # Let this through
+            success = False
+            result = None
+        except:
+            # Anything else is fatal
+            raise
+        else:
+            success = True
+        
+        after = self.reactor.seconds()
+        msg(
+            type="operation",
+            phase="end",
+            duration=after - before,
+            user=self.record.uid,
+            client_type=self._client_type,
+            client_id=self._client_id,
+            label=label,
+            success=success,
+        )
+        returnValue(result)
 
 
     def _monitorPubSub(self, home, params):
@@ -634,11 +913,19 @@
         factory = _PubSubClientFactory(
             self, "%s@%s" % (self.record.uid, host),
             self.record.password, service,
-            {params.pushkey: (home, home, "Calendar home")}, False)
+            {params.pushkey: (home, home, "Calendar home")}, False,
+            sigint=False)
+        self._pushFactories.append(factory)
         self.reactor.connectTCP(host, port, factory)
 
 
     @inlineCallbacks
+    def _unsubscribePubSub(self):
+        for factory in self._pushFactories:
+            yield factory.unsubscribeAll()
+
+
+    @inlineCallbacks
     def run(self):
         """
         Emulate a CalDAV client.
@@ -648,10 +935,14 @@
             principal = yield self.startup()
             hrefs = principal.getHrefProperties()
             calendarHome = hrefs[caldavxml.calendar_home_set].toString()
-            yield self._checkCalendarsForEvents(calendarHome)
+            if calendarHome is None:
+                raise MissingCalendarHome
+            yield self._checkCalendarsForEvents(calendarHome, firstTime=True)
             returnValue(calendarHome)
-        calendarHome = yield self._newOperation("startup", startup())
+        calendarHome = yield self._newOperation("startup: %s" % (self._client_type,), startup())
 
+        self.started = True
+
         # Start monitoring PubSub notifications, if possible.
         # _checkCalendarsForEvents populates self.xmpp if it finds
         # anything.
@@ -665,6 +956,12 @@
             yield self._calendarCheckLoop(calendarHome)
 
 
+    def stop(self):
+        """
+        Called before connections are closed, giving a chance to clean up
+        """
+        return self._unsubscribePubSub()
+
     def _makeSelfAttendee(self):
         attendee = Property(
             name=u'ATTENDEE',
@@ -689,70 +986,80 @@
         return organizer
 
 
+    @inlineCallbacks
     def addEventAttendee(self, href, attendee):
 
-        # Temporarily use some non-test names (some which will return
-        # many results, and others which will return fewer) because the
-        # test account names are all too similar
-        # name = attendee.parameterValue('CN').encode("utf-8")
-        # prefix = name[:4].lower()
-        prefix = random.choice(["chris", "cyru", "dre", "eric", "morg",
-            "well", "wilfr", "witz"])
-
-        email = attendee.parameterValue('EMAIL').encode("utf-8")
-
         event = self._events[href]
         vevent = event.vevent
 
-        # First try to discover some names to supply to the
-        # auto-completion
-        d = self._request(
-            MULTI_STATUS, 'REPORT', self.root + 'principals/',
-            Headers({'content-type': ['text/xml']}),
-            StringProducer(self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
-                    'displayname': prefix,
-                    'email': prefix,
-                    'firstname': prefix,
-                    'lastname': prefix,
-                    }))
-        d.addCallback(readBody)
-        def specific(ignored):
+        # Trigger auto-complete behavior
+        yield self._attendeeAutoComplete(vevent, attendee)
+
+        # If the event has no attendees, add ourselves as an attendee.
+        attendees = list(vevent.mainComponent().properties('ATTENDEE'))
+        if len(attendees) == 0:
+            # First add ourselves as a participant and as the
+            # organizer.  In the future for this event we should
+            # already have those roles.
+            vevent.mainComponent().addProperty(self._makeSelfOrganizer())
+            vevent.mainComponent().addProperty(self._makeSelfAttendee())
+        attendees.append(attendee)
+        vevent.mainComponent().addProperty(attendee)
+
+        # At last, upload the new event definition
+        response = yield self._request(
+            (NO_CONTENT, PRECONDITION_FAILED,),
+            'PUT',
+            self.root + href[1:].encode('utf-8'),
+            Headers({
+                    'content-type': ['text/calendar'],
+                    'if-match': [event.etag]}),
+            StringProducer(vevent.getTextWithTimezones(includeTimezones=True)),
+            method_label="PUT{organizer}"
+        )
+
+        # Finally, re-retrieve the event to update the etag
+        yield self._updateEvent(response, href)
+
+
+    @inlineCallbacks
+    def _attendeeAutoComplete(self, vevent, attendee):
+
+        if self._ATTENDEE_LOOKUPS:
+            # Temporarily use some non-test names (some which will return
+            # many results, and others which will return fewer) because the
+            # test account names are all too similar
+            # name = attendee.parameterValue('CN').encode("utf-8")
+            # prefix = name[:4].lower()
+            prefix = random.choice(["chris", "cyru", "dre", "eric", "morg",
+                "well", "wilfr", "witz"])
+    
+            email = attendee.parameterValue('EMAIL').encode("utf-8")
+    
+            # First try to discover some names to supply to the
+            # auto-completion
+            response = yield self._request(
+                MULTI_STATUS, 'REPORT', self.root + 'principals/',
+                Headers({'content-type': ['text/xml']}),
+                StringProducer(self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
+                        'displayname': prefix,
+                        'email': prefix,
+                        'firstname': prefix,
+                        'lastname': prefix,
+                        }),
+                method_label="REPORT{psearch}",
+            )
+            yield readBody(response)
+    
             # Now learn about the attendee's availability
-            return self.requestAvailability(
+            yield self.requestAvailability(
                 vevent.mainComponent().getStartDateUTC(),
                 vevent.mainComponent().getEndDateUTC(),
                 [self.email, u'mailto:' + email],
                 [vevent.resourceUID()])
-            return d
-        d.addCallback(specific)
-        def availability(ignored):
-            # If the event has no attendees, add ourselves as an attendee.
-            attendees = list(vevent.mainComponent().properties('ATTENDEE'))
-            if len(attendees) == 0:
-                # First add ourselves as a participant and as the
-                # organizer.  In the future for this event we should
-                # already have those roles.
-                vevent.mainComponent().addProperty(self._makeSelfOrganizer())
-                vevent.mainComponent().addProperty(self._makeSelfAttendee())
-            attendees.append(attendee)
-            vevent.mainComponent().addProperty(attendee)
 
-            # At last, upload the new event definition
-            d = self._request(
-                (NO_CONTENT, PRECONDITION_FAILED,),
-                'PUT',
-                self.root + href[1:].encode('utf-8'),
-                Headers({
-                        'content-type': ['text/calendar'],
-                        'if-match': [event.etag]}),
-                StringProducer(vevent.getTextWithTimezones(includeTimezones=True)))
-            return d
-        d.addCallback(availability)
-        # Finally, re-retrieve the event to update the etag
-        d.addCallback(self._updateEvent, href)
-        return d
 
-
+    @inlineCallbacks
     def changeEventAttendee(self, href, oldAttendee, newAttendee):
         event = self._events[href]
         vevent = event.vevent
@@ -768,56 +1075,62 @@
             headers.addRawHeader('if-schedule-tag-match', event.scheduleTag)
             okCodes = (NO_CONTENT, PRECONDITION_FAILED,)
 
-        d = self._request(
+        response = yield self._request(
             okCodes,
             'PUT',
             self.root + href[1:].encode('utf-8'),
-            headers, StringProducer(vevent.getTextWithTimezones(includeTimezones=True)))
-        d.addCallback(self._updateEvent, href)
-        return d
+            headers, StringProducer(vevent.getTextWithTimezones(includeTimezones=True)),
+            method_label="PUT{attendee}",
+        )
+        self._updateEvent(response, href)
 
 
+    @inlineCallbacks
     def deleteEvent(self, href):
         """
         Issue a DELETE for the given URL and remove local state
         associated with that event.
         """
-        d = self._request(
+        
+        self._removeEvent(href)
+
+        response = yield self._request(
             NO_CONTENT, 'DELETE', self.root + href[1:].encode('utf-8'))
+        returnValue(response)
 
-        calendar, uid = href.rsplit('/', 1)
-        del self._events[href]
-        del self._calendars[calendar + u'/'].events[uid]
 
-        return d
-
-
-    def addEvent(self, href, vcalendar):
+    @inlineCallbacks
+    def addEvent(self, href, vcalendar, invite=False):
         headers = Headers({
                 'content-type': ['text/calendar'],
                 })
-        d = self._request(
-            CREATED, 'PUT', self.root + href[1:].encode('utf-8'),
-            headers, StringProducer(vcalendar.getTextWithTimezones(includeTimezones=True)))
-        d.addCallback(self._localUpdateEvent, href, vcalendar)
-        return d
+        response = yield self._request(
+            CREATED,
+            'PUT',
+            self.root + href[1:].encode('utf-8'),
+            headers,
+            StringProducer(vcalendar.getTextWithTimezones(includeTimezones=True)),
+            method_label="PUT{organizer}" if invite else None,
+        )
+        self._localUpdateEvent(response, href, vcalendar)
 
+
     @inlineCallbacks
-    def addInvite(self, href, vcalendar):
+    def addInvite(self, href, vevent):
         """
         Add an event that is an invite - i.e., has attendees. We will do attendee lookups and freebusy
         checks on each attendee to simulate what happens when an organizer creates a new invite.
         """
         
         # Do lookup and free busy of each attendee (not self)
-        attendees = list(vcalendar.mainComponent().properties('ATTENDEE'))
+        attendees = list(vevent.mainComponent().properties('ATTENDEE'))
         for attendee in attendees:
             if attendee.value() == self.uuid:
                 continue
-            yield self.checkAttendee(vcalendar, attendee)
+            yield self._attendeeAutoComplete(vevent, attendee)
         
         # Now do a normal PUT
-        yield self.addEvent(href, vcalendar)
+        yield self.addEvent(href, vevent, invite=True)
 
 
     def _localUpdateEvent(self, response, href, vcalendar):
@@ -834,58 +1147,17 @@
         return self._updateEvent(None, href)
 
 
+    @inlineCallbacks
     def _updateEvent(self, ignored, href):
-        d = self._request(OK, 'GET', self.root + href[1:].encode('utf-8'))
-        def getETag(response):
-            headers = response.headers
-            etag = headers.getRawHeaders('etag')[0]
-            scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
-            return readBody(response).addCallback(
-                lambda body: (etag, scheduleTag, body))
-        d.addCallback(getETag)
-        def record((etag, scheduleTag, body)):
-            self.eventChanged(href, etag, scheduleTag, body)
-        d.addCallback(record)
-        return d
+        response = yield self._request(OK, 'GET', self.root + href[1:].encode('utf-8'))
+        headers = response.headers
+        etag = headers.getRawHeaders('etag')[0]
+        scheduleTag = headers.getRawHeaders('schedule-tag', [None])[0]
+        body = yield readBody(response)
+        self.eventChanged(href, etag, scheduleTag, body)
 
 
     @inlineCallbacks
-    def checkAttendee(self, vcalendar, attendee):
-        """
-        This is what the client does when a user does attendee auto-complete whilst creating an
-        event invite. We will run this once for each attendee in a new invite.
-        """
-
-        # Temporarily use some non-test names (some which will return
-        # many results, and others which will return fewer) because the
-        # test account names are all too similar
-        # name = attendee.parameterValue('CN').encode("utf-8")
-        # prefix = name[:4].lower()
-        prefix = random.choice(["chris", "cyru", "dre", "eric", "morg",
-            "well", "wilfr", "witz"])
-
-        email = attendee.parameterValue('EMAIL').encode("utf-8")
-
-        # First try to discover some names to supply to the
-        # auto-completion - we will ignore the response
-        yield self._request(
-            MULTI_STATUS, 'REPORT', self.root + 'principals/',
-            Headers({'content-type': ['text/xml']}),
-            StringProducer(self._USER_LIST_PRINCIPAL_PROPERTY_SEARCH % {
-                    'displayname': prefix,
-                    'email': prefix,
-                    'firstname': prefix,
-                    'lastname': prefix,
-                    }))
-
-        # Now learn about the attendee's availability
-        yield self.requestAvailability(
-            vcalendar.mainComponent().getStartDateUTC(),
-            vcalendar.mainComponent().getEndDateUTC(),
-            [u'mailto:' + email],
-            [vcalendar.resourceUID()])
-
-
     def requestAvailability(self, start, end, users, mask=set()):
         """
         Issue a VFREEBUSY request for I{roughly} the given date range for the
@@ -931,7 +1203,7 @@
         end = end.getText()
         now = PyCalendarDateTime.getNowUTC().getText()
 
-        d = self._request(
+        response = yield self._request(
             OK, 'POST', outbox,
             Headers({
                     'content-type': ['text/calendar'],
@@ -945,12 +1217,404 @@
                     'event-mask': maskStr,
                     'start': start,
                     'end': end,
-                    'now': now}))
-        d.addCallback(readBody)
-        return d
+                    'now': now}),
+            method_label="POST{fb}",
+        )
+        body = yield readBody(response)
+        returnValue(body)
 
 
 
+class OS_X_10_6(BaseAppleClient):
+    """
+    Implementation of the OS X 10.6 iCal network behavior.
+
+    Anything OS X 10.6 iCal does on its own, or any particular
+    network behaviors it takes in response to a user action, belong on
+    this class.
+
+    Usage-profile based behaviors ("the user modifies an event every
+    3.2 minutes") belong elsewhere.
+    """
+
+    _client_type = "OS X 10.6"
+
+    USER_AGENT = "DAVKit/4.0.3 (732); CalendarStore/4.0.3 (991); iCal/4.0.3 (1388); Mac OS X/10.6.4 (10F569)"
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by Snow
+    # Leopard iCal.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+    
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 200
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
+
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = False
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = "OS_X_10_6"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = None
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+    @inlineCallbacks
+    def startup(self):
+
+        # PROPFIND /principals/users/<uid> to retrieve /principals/__uids__/<guid>
+        response = yield self._principalPropfindInitial(self.record.uid)
+        hrefs = response.getHrefProperties()
+        self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._principalPropfind()
+
+        hrefs = principal.getHrefProperties()
+
+        # Remember our outbox and notifications
+        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+        try:
+            self.notificationURL = hrefs[csxml.notification_URL].toString()
+        except KeyError:
+            self.notificationURL = None
+
+        # Remember our own email-like principal address
+        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
+            if principalURL.toString().startswith(u"mailto:"):
+                self.email = principalURL.toString()
+            elif principalURL.toString().startswith(u"urn:"):
+                self.uuid = principalURL.toString()
+        if self.email is None:
+            raise ValueError("Cannot operate without a mail-style principal URL")
+
+        # Do another kind of thing I guess
+        principalCollection = hrefs[davxml.principal_collection_set].toString()
+        (yield self._principalSearchPropertySetReport(principalCollection))
+
+        returnValue(principal)
+
+
+class OS_X_10_7(BaseAppleClient):
+    """
+    Implementation of the OS X 10.7 iCal network behavior.
+    """
+
+    _client_type = "OS X 10.7"
+
+    USER_AGENT = "CalendarStore/5.0.2 (1166); iCal/5.0.2 (1571); Mac OS X/10.7.3 (11D50)"
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by Snow
+    # Leopard iCal.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+    
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 50
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = True
+
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = False
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = True
+
+    # Request body data
+    _LOAD_PATH = "OS_X_10_7"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PRINCIPAL_EXPAND = loadRequestBody(_LOAD_PATH, 'startup_principal_expand')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_TIMEZONE = loadRequestBody(_LOAD_PATH, 'startup_calendar_timezone_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+    _POLL_CALENDAR_SYNC_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_sync')
+    _POLL_NOTIFICATION_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_NOTIFICATION_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_notification_propfind_d1')
+
+    _USER_LIST_PRINCIPAL_PROPERTY_SEARCH = loadRequestBody(_LOAD_PATH, 'user_list_principal_property_search')
+    _POST_AVAILABILITY = loadRequestBody(_LOAD_PATH, 'post_availability')
+
+
+    def _addDefaultHeaders(self, headers):
+        """
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        """
+        
+        super(OS_X_10_7, self)._addDefaultHeaders(headers)
+        headers.setRawHeaders('Accept', ['*/*'])
+        headers.setRawHeaders('Accept-Language', ['en-us'])
+        headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+        headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+    @inlineCallbacks
+    def startup(self):
+
+        # PROPFIND well-known - ignore
+        yield self._startupPropfindWellKnown()
+        
+        # PROPFIND / - ignore
+        yield self._startupPropfindRoot()
+        
+        # PROPFIND /principals/users/<uid> to retrieve /principals/__uids__/<guid>
+        response = yield self._principalPropfindInitial(self.record.uid)
+        hrefs = response.getHrefProperties()
+        self.principalURL = hrefs[davxml.principal_URL].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._principalPropfind()
+
+        hrefs = principal.getHrefProperties()
+
+        # Remember our outbox and notifications
+        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+        try:
+            self.notificationURL = hrefs[csxml.notification_URL].toString()
+        except KeyError:
+            self.notificationURL = None
+
+        # Remember our own email-like principal address
+        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
+            if principalURL.toString().startswith(u"mailto:"):
+                self.email = principalURL.toString()
+            elif principalURL.toString().startswith(u"urn:"):
+                self.uuid = principalURL.toString()
+        if self.email is None:
+            raise ValueError("Cannot operate without a mail-style principal URL")
+
+        # Do another kind of thing I guess
+        principalCollection = hrefs[davxml.principal_collection_set].toString()
+        yield self._principalSearchPropertySetReport(principalCollection)
+
+        returnValue(principal)
+
+
+
+class iOS_5(BaseAppleClient):
+    """
+    Implementation of the iOS 5 network behavior.
+    """
+
+    _client_type = "iOS 5"
+
+    USER_AGENT = "iOS/5.1 (9B179) dataaccessd/1.0"
+
+    # The default interval, used if none is specified in external
+    # configuration.  This is also the actual value used by Snow
+    # Leopard iCal.
+    CALENDAR_HOME_POLL_INTERVAL = 15 * 60
+    
+    # The maximum number of resources to retrieve in a single multiget
+    MULTIGET_BATCH_SIZE = 50
+
+    # Override and turn on if client supports Sync REPORT
+    _SYNC_REPORT = False
+
+    # Override and turn on if client syncs using time-range queries
+    _SYNC_TIMERANGE = True
+
+    # Override and turn off if client does not support attendee lookups
+    _ATTENDEE_LOOKUPS = False
+
+    # Request body data
+    _LOAD_PATH = "iOS_5"
+
+    _STARTUP_WELL_KNOWN = loadRequestBody(_LOAD_PATH, 'startup_well_known')
+    _STARTUP_PRINCIPAL_PROPFIND_INITIAL = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind_initial')
+    _STARTUP_PRINCIPAL_PROPFIND = loadRequestBody(_LOAD_PATH, 'startup_principal_propfind')
+    _STARTUP_PRINCIPALS_REPORT = loadRequestBody(_LOAD_PATH, 'startup_principals_report')
+    _STARTUP_PROPPATCH_CALENDAR_COLOR = loadRequestBody(_LOAD_PATH, 'startup_calendar_color_proppatch')
+    _STARTUP_PROPPATCH_CALENDAR_ORDER = loadRequestBody(_LOAD_PATH, 'startup_calendar_order_proppatch')
+
+    _POLL_CALENDARHOME_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendarhome_propfind')
+    _POLL_CALENDAR_PROPFIND = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind')
+    _POLL_CALENDAR_VEVENT_TR_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vevent_tr_query')
+    _POLL_CALENDAR_VTODO_QUERY = loadRequestBody(_LOAD_PATH, 'poll_calendar_vtodo_query')
+    _POLL_CALENDAR_PROPFIND_D1 = loadRequestBody(_LOAD_PATH, 'poll_calendar_propfind_d1')
+    _POLL_CALENDAR_MULTIGET_REPORT = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget')
+    _POLL_CALENDAR_MULTIGET_REPORT_HREF = loadRequestBody(_LOAD_PATH, 'poll_calendar_multiget_hrefs')
+
+
+    def _addDefaultHeaders(self, headers):
+        """
+        Add the clients default set of headers to ones being used in a request.
+        Default is to add User-Agent, sub-classes should override to add other
+        client specific things, Accept etc.
+        """
+        
+        super(iOS_5, self)._addDefaultHeaders(headers)
+        headers.setRawHeaders('Accept', ['*/*'])
+        headers.setRawHeaders('Accept-Language', ['en-us'])
+        headers.setRawHeaders('Accept-Encoding', ['gzip,deflate'])
+        headers.setRawHeaders('Connection', ['keep-alive'])
+
+
+    @inlineCallbacks
+    def _principalPropfindInitial(self):
+        """
+        Issue a PROPFIND on the /principals/ URL to retrieve
+        the /principals/__uids__/<guid> principal URL
+        """
+        result = yield self._propfind(
+            '/principals/',
+            self._STARTUP_PRINCIPAL_PROPFIND_INITIAL,
+        )
+        returnValue(result['/principals/'])
+
+
+    @inlineCallbacks
+    def _pollFirstTime1(self, homeNode, calendars):
+        # Patch calendar properties
+        for cal in calendars:
+            if cal.name != "inbox":
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_COLOR)
+                yield self._proppatch(cal.url, self._STARTUP_PROPPATCH_CALENDAR_ORDER)
+
+
+    def _pollFirstTime2(self):
+        # Nothing here
+        return succeed(None)
+
+
+    def _updateCalendar(self, calendar, newToken):
+        """
+        Update the local cached data for a calendar in an appropriate manner.
+        """
+        if calendar.name == "inbox":
+            # Inbox is done as a PROPFIND Depth:1
+            return self._updateCalendar_PROPFIND(calendar, newToken)
+        elif "VEVENT" in calendar.componentTypes:
+            # VEVENTs done as time-range VEVENT-only queries
+            return self._updateCalendar_VEVENT(calendar, newToken)
+        elif "VTODO" in calendar.componentTypes:
+            # VTODOs done as VTODO-only queries
+            return self._updateCalendar_VTODO(calendar, newToken)
+
+
+    @inlineCallbacks
+    def _updateCalendar_VEVENT(self, calendar, newToken):
+        """
+        Sync all locally cached VEVENTs using a VEVENT-only time-range query.
+        """
+
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+        now = PyCalendarDateTime.getNowUTC()
+        now.setDateOnly(True)
+        now.offsetMonth(-1) # 1 month back default
+        result = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_VEVENT_TR_QUERY % {"start-date":now.getText()},
+            depth='1',
+            method_label="REPORT{vevent}",
+        )
+
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def _updateCalendar_VTODO(self, calendar, newToken):
+        """
+        Sync all locally cached VTODOs using a VTODO-only query.
+        """
+
+        # Grab old hrefs prior to the PROPFIND so we sync with the old state. We need this because
+        # the sim can fire a PUT between the PROPFIND and when process the removals.
+        old_hrefs = set([calendar.url + child for child in calendar.events.keys()])
+
+        result = yield self._report(
+            calendar.url,
+            self._POLL_CALENDAR_VTODO_QUERY,
+            depth='1',
+            method_label="REPORT{vtodo}",
+        )
+
+        yield self._updateApplyChanges(calendar, result, old_hrefs)
+
+        # Now update calendar to the new token
+        self._calendars[calendar.url].changeToken = newToken
+
+
+    @inlineCallbacks
+    def startup(self):
+
+        # PROPFIND well-known - ignore
+        yield self._startupPropfindWellKnown()
+        
+        # PROPFIND / - ignore
+        yield self._startupPropfindRoot()
+        
+        # PROPFIND /principals/ to retrieve /principals/__uids__/<guid>
+        response = yield self._principalPropfindInitial()
+        hrefs = response.getHrefProperties()
+        self.principalURL = hrefs[davxml.current_user_principal].toString()
+
+        # Using the actual principal URL, retrieve principal information
+        principal = yield self._principalPropfind()
+
+        hrefs = principal.getHrefProperties()
+
+        # Remember our outbox and ignore notifications
+        self.outbox = hrefs[caldavxml.schedule_outbox_URL].toString()
+        self.notificationURL = None
+
+        # Remember our own email-like principal address
+        for principalURL in hrefs[caldavxml.calendar_user_address_set]:
+            if principalURL.toString().startswith(u"mailto:"):
+                self.email = principalURL.toString()
+            elif principalURL.toString().startswith(u"urn:"):
+                self.uuid = principalURL.toString()
+        if self.email is None:
+            raise ValueError("Cannot operate without a mail-style principal URL")
+
+        # Do another kind of thing I guess
+        principalCollection = hrefs[davxml.principal_collection_set].toString()
+        yield self._principalSearchPropertySetReport(principalCollection)
+
+        returnValue(principal)
+
+
 class RequestLogger(object):
     format = u"%(user)s request %(code)s%(success)s[%(duration)5.2f s] %(method)8s %(url)s"
     success = u"\N{CHECK MARK}"
@@ -973,7 +1637,7 @@
             print (self.format % formatArgs).encode('utf-8')
 
 
-    def report(self):
+    def report(self, output):
         pass
 
 
@@ -995,13 +1659,13 @@
     addObserver(RequestLogger().observe)
 
     from sim import _DirectoryRecord
-    client = SnowLeopard(
+    client = OS_X_10_6(
         reactor, 'http://127.0.0.1:8008/', 
         _DirectoryRecord(
             u'user01', u'user01', u'User 01', u'user01 at example.org'),
         auth)
     d = client.run()
-    d.addErrback(err, "Snow Leopard client run() problem")
+    d.addErrback(err, "10.6 client run() problem")
     d.addCallback(lambda ignored: reactor.stop())
     reactor.run()
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/logger.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/logger.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/logger.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 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.
@@ -15,10 +15,11 @@
 #
 ##
 
-from contrib.performance.stats import mean, median
+from contrib.performance.stats import mean, median, stddev
 
 class SummarizingMixin(object):
-    def printHeader(self, fields):
+
+    def printHeader(self, output, fields):
         """
         Print a header for the summarization data which will be reported.
 
@@ -32,29 +33,47 @@
         for (label, width) in fields:
             format.append('%%%ds' % (width,))
             labels.append(label)
-        print ' '.join(format) % tuple(labels)
+        header = ' '.join(format) % tuple(labels)
+        output.write("%s\n" % header)
+        output.write("%s\n" % ("-" * len(header),))
 
 
     def _summarizeData(self, operation, data):
         failed = 0
-        threesec = 0
+        thresholds = [0] * len(self._thresholds)
         durations = []
         for (success, duration) in data:
             if not success:
                 failed += 1
-            if duration > 3:
-                threesec += 1
+            for ctr, item in enumerate(self._thresholds):
+                threshold, _ignore_fail_at = item
+                if duration > threshold:
+                    thresholds[ctr] += 1
             durations.append(duration)
 
-        return operation, len(data), failed, threesec, mean(durations), median(durations)
+        # Determine PASS/FAIL
+        failure = False
+        count = len(data)
+        
+        if failed * 100.0 / count > self._fail_cut_off:
+            failure = True
 
+        for ctr, item in enumerate(self._thresholds):
+            _ignore_threshold, fail_at = item
+            if thresholds[ctr] * 100.0 / count > fail_at:
+                failure = True
 
-    def _printRow(self, formats, values):
+        return (operation, count, failed,) + \
+                tuple(thresholds) + \
+                (mean(durations), median(durations), stddev(durations), "FAIL" if failure else "")
+
+
+    def _printRow(self, output, formats, values):
         format = ' '.join(formats)
-        print format % values
+        output.write("%s\n" % format % values)
 
 
-    def printData(self, formats, perOperationTimes):
+    def printData(self, output, formats, perOperationTimes):
         """
         Print one or more rows of data with the given formatting.
 
@@ -66,4 +85,4 @@
             (C{True} if so, C{False} if not) and how long the operation took.
         """
         for method, data in perOperationTimes:
-            self._printRow(formats, self._summarizeData(method, data))
+            self._printRow(output, formats, self._summarizeData(method, data))

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/population.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/population.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,6 +1,6 @@
 # -*- test-case-name: contrib.performance.loadtest.test_population -*-
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2012 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.
@@ -25,7 +25,11 @@
 
 from tempfile import mkdtemp
 from itertools import izip
+from datetime import datetime
+import os
 
+from twisted.internet.defer import DeferredList
+from twisted.python.failure import Failure
 from twisted.python.filepath import FilePath
 from twisted.python.util import FancyEqMixin
 from twisted.python.log import msg, err
@@ -35,7 +39,7 @@
 from contrib.performance.stats import mean, median, stddev, mad
 from contrib.performance.loadtest.trafficlogger import loggedReactor
 from contrib.performance.loadtest.logger import SummarizingMixin
-from contrib.performance.loadtest.ical import SnowLeopard, RequestLogger
+from contrib.performance.loadtest.ical import OS_X_10_6, RequestLogger
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
 
 
@@ -161,6 +165,7 @@
         self._stopped = False
         self.workerIndex = workerIndex
         self.workerCount = workerCount
+        self.clients = []
 
         TimezoneCache.create()
 
@@ -194,32 +199,42 @@
         disregarded (as some are expected, as the simulation will always stop
         while some requests are in flight).
         """
+
+        # Give all the clients a chance to stop (including unsubscribe from push)
+        deferreds = []
+        for client in self.clients:
+            deferreds.append(client.stop())
         self._stopped = True
+        return DeferredList(deferreds)
 
 
-    def add(self, numClients):
+    def add(self, numClients, clientsPerUser):
         for _ignore_n in range(numClients):
             number = self._nextUserNumber()
-            clientType = self._pop.next()
-            if (number % self.workerCount) != self.workerIndex:
-                # If we're in a distributed work scenario and we are worker N,
-                # we have to skip all but every Nth request (since every node
-                # runs the same arrival policy).
-                continue
+            
+            for _ignore_peruser in range(clientsPerUser):
+                clientType = self._pop.next()
+                if (number % self.workerCount) != self.workerIndex:
+                    # If we're in a distributed work scenario and we are worker N,
+                    # we have to skip all but every Nth request (since every node
+                    # runs the same arrival policy).
+                    continue
+    
+                _ignore_user, auth = self._createUser(number)
+    
+                reactor = loggedReactor(self.reactor)
+                client = clientType.new(
+                    reactor, self.server, self.getUserRecord(number), auth)
+                self.clients.append(client)
+                d = client.run()
+                d.addErrback(self._clientFailure, reactor)
+    
+                for profileType in clientType.profileTypes:
+                    profile = profileType(reactor, self, client, number)
+                    if profile.enabled:
+                        d = profile.run()
+                        d.addErrback(self._profileFailure, profileType, reactor)
 
-            _ignore_user, auth = self._createUser(number)
-
-            reactor = loggedReactor(self.reactor)
-            client = clientType.new(
-                reactor, self.server, self.getUserRecord(number), auth)
-            d = client.run()
-            d.addErrback(self._clientFailure, reactor)
-
-            for profileType in clientType.profileTypes:
-                profile = profileType(reactor, self, client, number)
-                if profile.enabled:
-                    d = profile.run()
-                    d.addErrback(self._profileFailure, profileType, reactor)
         # XXX this status message is prone to be slightly inaccurate, but isn't
         # really used by much anyway.
         msg(type="status", clientCount=self._user - 1)
@@ -242,6 +257,9 @@
             where = self._dumpLogs(reactor, reason)
             err(reason, "Client stopped with error; recent traffic in %r" % (
                     where.path,))
+            if not isinstance(reason, Failure):
+                reason = Failure(reason)
+            msg(type="client-failure", reason="%s: %s" % (reason.type, reason.value,))
 
 
     def _profileFailure(self, reason, profileType, reactor):
@@ -253,17 +271,18 @@
 
 
 class SmoothRampUp(object):
-    def __init__(self, reactor, groups, groupSize, interval):
+    def __init__(self, reactor, groups, groupSize, interval, clientsPerUser):
         self.reactor = reactor
         self.groups = groups
         self.groupSize = groupSize
         self.interval = interval
+        self.clientsPerUser = clientsPerUser
 
 
     def run(self, simulator):
         for i in range(self.groups):
             self.reactor.callLater(
-                self.interval * i, simulator.add, self.groupSize)
+                self.interval * i, simulator.add, self.groupSize, self.clientsPerUser)
 
 
 
@@ -271,9 +290,11 @@
     def observe(self, event):
         if event.get('type') == 'response':
             self.eventReceived(event)
+        elif event.get('type') == 'client-failure':
+            self.clientFailure(event)
 
 
-    def report(self):
+    def report(self, output):
         pass
 
 
@@ -297,7 +318,10 @@
             del self._times[:100]
 
 
+    def clientFailure(self, event):
+        pass
 
+
 class ReportStatistics(StatisticsBase, SummarizingMixin):
     """
 
@@ -306,86 +330,139 @@
         reported as the number of users in the simulation.
 
     """
+
+    # the response time thresholds to display together with failing % count threshold
+    _thresholds = (
+        (0.5, 100.0),  
+        (  1, 100.0),  
+        (  3,   5.0),  
+        (  5,   1.0),
+        ( 10,   0.5),
+    )
+    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure 
+
     _fields = [
-        ('request', 10, '%10s'),
+        ('request', -20, '%-20s'),
         ('count', 8, '%8s'),
         ('failed', 8, '%8s'),
-        ('>3sec', 8, '%8s'),
+    ]
+    
+    for threshold, _ignore_fail_at in _thresholds:
+        _fields.append(('>%g sec' % (threshold,), 10, '%10s'))
+
+    _fields.extend([
         ('mean', 8, '%8.4f'),
         ('median', 8, '%8.4f'),
-        ]
+        ('stddev', 8, '%8.4f'),
+        ('STATUS', 8, '%8s'),
+    ])
 
     def __init__(self):
         self._perMethodTimes = {}
         self._users = set()
+        self._clients = set()
+        self._failed_clients = []
+        self._startTime = datetime.now()
 
 
     def countUsers(self):
         return len(self._users)
 
 
+    def countClients(self):
+        return len(self._clients)
+
+
+    def countClientFailures(self):
+        return len(self._failed_clients)
+
+
     def eventReceived(self, event):
         dataset = self._perMethodTimes.setdefault(event['method'], [])
         dataset.append((event['success'], event['duration']))
         self._users.add(event['user'])
+        self._clients.add(event['client_id'])
 
 
-    def printMiscellaneous(self, items):
-        for k, v in sorted(items.iteritems()):
-            print k.title(), ':', v
+    def clientFailure(self, event):
+        self._failed_clients.append(event['reason'])
 
 
-    def report(self):
-        print
-        self.printMiscellaneous({'users': self.countUsers()})
-        self.printHeader([
+    def printMiscellaneous(self, output, items):
+        maxColumnWidth = str(len(max(items.iterkeys(), key=len)))
+        fmt = "%"+maxColumnWidth+"s : %-s\n"
+        for k in sorted(items.iterkeys()):
+            output.write(fmt % (k.title(), items[k],))
+
+
+    def report(self, output):
+        output.write("\n")
+        output.write("** REPORT **\n")
+        output.write("\n")
+        runtime = datetime.now() - self._startTime
+        cpu = os.times()
+        cpuUser = cpu[0] + cpu[2]
+        cpuSys = cpu[1] + cpu[3]
+        cpuTotal = cpuUser + cpuSys
+        runHours, remainder = divmod(runtime.seconds, 3600)
+        runMinutes, runSeconds = divmod(remainder, 60)
+        cpuHours, remainder = divmod(cpuTotal, 3600)
+        cpuMinutes, cpuSeconds = divmod(remainder, 60)
+        items = {
+            'Users': self.countUsers(),
+            'Clients': self.countClients(),
+            'Start time': self._startTime.strftime('%m/%d %H:%M:%S'),
+            'Run time': "%02d:%02d:%02d" % (runHours,runMinutes,runSeconds),
+            'CPU Time': "user %-5.2f sys %-5.2f total %02d:%02d:%02d" % (cpuUser, cpuSys, cpuHours, cpuMinutes, cpuSeconds,)
+        }
+        if self.countClientFailures() > 0:
+            items['Failed clients'] = self.countClientFailures()
+            for ctr, reason in enumerate(self._failed_clients, 1):
+                items['Failure #%d' % (ctr,)] = reason
+        self.printMiscellaneous(output, items)
+        output.write("\n")
+        self.printHeader(output, [
                 (label, width)
                 for (label, width, _ignore_fmt)
                 in self._fields])
-        self.printData(
+        self.printData(output,
             [fmt for (label, width, fmt) in self._fields],
             sorted(self._perMethodTimes.items()))
 
-    _FAILED_REASON = "Greater than %(cutoff)0.f%% %(method)s failed"
-    _THREESEC_REASON = "Greater than %(cutoff)0.f%% %(method)s exceeded 3 second response time"
-    _FIVESEC_REASON = "Greater than %(cutoff)0.f%% %(method)s exceeded 5 second response time"
+    _FAILED_REASON = "Greater than %(cutoff)g%% %(method)s failed"
 
+    _REASON_1 = "Greater than %(cutoff)g%% %(method)s exceeded "
+    _REASON_2 = "%g second response time"
+
     def failures(self):
         # TODO
         reasons = []
 
-        # Upper limit on ratio of failed requests to total requests
-        failCutoff = 0.01
-
-        # Upper limit on ratio of >3sec requests to total requests
-        threeSecCutoff = 0.05
-
-        # Upper limit on ratio of >5sec requests to total requests
-        fiveSecCutoff = 0.01
-
         for (method, times) in self._perMethodTimes.iteritems():
             failures = 0
-            threeSec = 0
-            fiveSec = 0
+            overDurations = [0] * len(self._thresholds)
 
             for success, duration in times:
                 if not success:
                     failures += 1
-                if duration > 5:
-                    fiveSec += 1
-                elif duration > 3:
-                    threeSec += 1
+                for ctr, item in enumerate(self._thresholds):
+                    threshold, _ignore_fail_at = item
+                    if duration > threshold:
+                        overDurations[ctr] += 1
 
             checks = [
-                (failures, failCutoff, self._FAILED_REASON),
-                (threeSec, threeSecCutoff, self._THREESEC_REASON),
-                (fiveSec, fiveSecCutoff, self._FIVESEC_REASON),
+                (failures, self._fail_cut_off, self._FAILED_REASON),
                 ]
+            
+            for ctr, item in enumerate(self._thresholds):
+                threshold, fail_at = item
+                checks.append(
+                    (overDurations[ctr], fail_at, self._REASON_1 + self._REASON_2 % (threshold,))
+                )
 
             for count, cutoff, reason in checks:
-                if count / len(times) > cutoff:
-                    reasons.append(reason % dict(
-                            method=method, cutoff=cutoff * 100))
+                if count * 100.0 / len(times) > cutoff:
+                    reasons.append(reason % dict(method=method, cutoff=cutoff))
 
         return reasons
 
@@ -410,7 +487,7 @@
     populator = Populator(r)
     parameters = PopulationParameters()
     parameters.addClient(
-        1, ClientType(SnowLeopard, [Eventer, Inviter, Accepter]))
+        1, ClientType(OS_X_10_6, [Eventer, Inviter, Accepter]))
     simulator = CalendarClientSimulator(
         populator, parameters, reactor, '127.0.0.1', 8008)
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/profiles.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/profiles.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -90,8 +90,15 @@
         lag = context.get('lag', None)
 
         before = self._reactor.seconds()
-        msg(type="operation", phase="start",
-            user=self._client.record.uid, label=label, lag=lag)
+        msg(
+            type="operation",
+            phase="start",
+            user=self._client.record.uid,
+            client_type=self._client._client_type,
+            client_id=self._client._client_id,
+            label=label,
+            lag=lag,
+        )
 
         def finished(passthrough):
             success = not isinstance(passthrough, Failure)
@@ -99,8 +106,16 @@
                 passthrough.trap(IncorrectResponseCode)
                 passthrough = passthrough.value.response
             after = self._reactor.seconds()
-            msg(type="operation", phase="end", duration=after - before,
-                user=self._client.record.uid, label=label, success=success)
+            msg(
+                type="operation",
+                phase="end",
+                duration=after - before,
+                user=self._client.record.uid,
+                client_type=self._client._client_type,
+                client_id=self._client._client_id,
+                label=label,
+                success=success,
+            )
             return passthrough
         deferred.addBoth(finished)
         return deferred
@@ -122,7 +137,10 @@
 
     def iterate():
         d = function()
-        d.addCallbacks(repeat, result.errback)
+        if d is not None:
+            d.addCallbacks(repeat, result.errback)
+        else:
+            repeat(None)
 
     repeat(None)
     return result
@@ -198,6 +216,10 @@
             otherwise a L{Deferred} which fires when the attendee
             change has been made.
         """
+        
+        if not self._client.started:
+            return succeed(None)
+            
         # Find calendars which are eligible for invites
         calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
 
@@ -341,6 +363,10 @@
             otherwise a L{Deferred} which fires when the attendee
             change has been made.
         """
+
+        if not self._client.started:
+            return succeed(None)
+            
         # Find calendars which are eligible for invites
         calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
 
@@ -491,16 +517,17 @@
 
     def _handleReply(self, href):
         d = self._client.deleteEvent(href)
-        def finished(passthrough):
-            self._accepting.remove(href)
-            if isinstance(passthrough, Failure):
-                passthrough.trap(IncorrectResponseCode)
-                passthrough = passthrough.response
-            return passthrough
-        d.addBoth(finished)
+        d.addBoth(self._finishRemoveAccepting, href)
         return self._newOperation("reply done", d)
 
 
+    def _finishRemoveAccepting(self, passthrough, href):
+        self._accepting.remove(href)
+        if isinstance(passthrough, Failure):
+            passthrough.trap(IncorrectResponseCode)
+            passthrough = passthrough.value.response
+        return passthrough
+
     def _handleCancel(self, href):
 
         uid = self._client._events[href].getUID()
@@ -514,13 +541,7 @@
                         if uid == event.getUID():
                             return self._client.deleteEvent(event.url)
         d.addCallback(removed)
-        def finished(passthrough):
-            self._accepting.remove(href)
-            if isinstance(passthrough, Failure):
-                passthrough.trap(IncorrectResponseCode)
-                passthrough = passthrough.response
-            return passthrough
-        d.addBoth(finished)
+        d.addBoth(self._finishRemoveAccepting, href)
         return self._newOperation("cancelled", d)
 
 
@@ -578,6 +599,9 @@
 
 
     def _addEvent(self):
+        if not self._client.started:
+            return succeed(None)
+            
         calendars = self._calendarsOfType(caldavxml.calendar, "VEVENT")
 
         while calendars:
@@ -639,6 +663,9 @@
 
 
     def _addTask(self):
+        if not self._client.started:
+            return succeed(None)
+            
         calendars = self._calendarsOfType(caldavxml.calendar, "VTODO")
 
         while calendars:
@@ -674,15 +701,33 @@
 
     lagFormat = u'{lag %5.2f ms}'
 
+    # the response time thresholds to display together with failing % count threshold
+    _thresholds = (
+        (0.5, 100.0),  
+        (  1, 100.0),  
+        (  3, 100.0),  
+        (  5, 100.0),
+        ( 10, 100.0),
+    )
+    _lag_cut_off = 1.0      # Maximum allowed median scheduling latency, seconds 
+    _fail_cut_off = 1.0     # % of total count at which failed requests will cause a failure 
+
     _fields = [
-        ('operation', 10, '%10s'),
+        ('operation', -20, '%-20s'),
         ('count', 8, '%8s'),
         ('failed', 8, '%8s'),
-        ('>3sec', 8, '%8s'),
+    ]
+    
+    for threshold, _ignore_fail_at in _thresholds:
+        _fields.append(('>%g sec' % (threshold,), 10, '%10s'))
+
+    _fields.extend([
         ('mean', 8, '%8.4f'),
         ('median', 8, '%8.4f'),
-        ('avglag (ms)', 8, '%8.4f'),
-        ]
+        ('stddev', 8, '%8.4f'),
+        ('avglag (ms)', 12, '%12.4f'),
+        ('STATUS', 8, '%8s'),
+    ])
 
     def __init__(self, outfile=None):
         self._perOperationTimes = {}
@@ -713,16 +758,17 @@
 
     def _summarizeData(self, operation, data):
         avglag = mean(self._perOperationLags.get(operation, [0.0])) * 1000.0
-        return SummarizingMixin._summarizeData(self, operation, data) + (avglag,)
+        data = SummarizingMixin._summarizeData(self, operation, data)
+        return data[:-1] + (avglag,) + data[-1:]
 
 
-    def report(self):
-        print
-        self.printHeader([
+    def report(self, output):
+        output.write("\n")
+        self.printHeader(output, [
                 (label, width)
                 for (label, width, _ignore_fmt)
                 in self._fields])
-        self.printData(
+        self.printData(output,
             [fmt for (label, width, fmt) in self._fields],
             sorted(self._perOperationTimes.items()))
 
@@ -732,21 +778,15 @@
     def failures(self):
         reasons = []
 
-        # Maximum allowed median scheduling latency, seconds
-        lagCutoff = 1.0
-
-        # Maximum allowed ratio of failed operations
-        failCutoff = 0.01
-
         for operation, lags in self._perOperationLags.iteritems():
-            if median(lags) > lagCutoff:
+            if median(lags) > self._lag_cut_off:
                 reasons.append(self._LATENCY_REASON % dict(
-                        operation=operation.upper(), cutoff=lagCutoff * 1000))
+                        operation=operation.upper(), cutoff=self._lag_cut_off * 1000))
 
         for operation, times in self._perOperationTimes.iteritems():
             failures = len([success for (success, _ignore_duration) in times if not success])
-            if failures / len(times) > failCutoff:
+            if failures * 100.0 / len(times) > self._fail_cut_off:
                 reasons.append(self._FAILED_REASON % dict(
-                        operation=operation.upper(), cutoff=failCutoff * 100))
+                        operation=operation.upper(), cutoff=self._fail_cut_off))
 
         return reasons

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:calendar-multiget xmlns:x0="urn:ietf:params:xml:ns:caldav" xmlns:x1="DAV:">
-  <x1:prop>
-    <x1:getetag/>
-    <x0:calendar-data/>
-    <x0:schedule-tag/>
-  </x1:prop>
-%(hrefs)s
-</x0:calendar-multiget>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:calendar-multiget xmlns:x0="urn:ietf:params:xml:ns:caldav" xmlns:x1="DAV:">
+  <x1:prop>
+    <x1:getetag/>
+    <x0:calendar-data/>
+    <x0:schedule-tag/>
+  </x1:prop>
+%(hrefs)s
+</x0:calendar-multiget>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1 +0,0 @@
-  <x1:href>%(href)s</x1:href>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_multiget_hrefs.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1 @@
+  <x1:href>%(href)s</x1:href>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
-    <A:sync-token/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:getetag/>
-  <x0:resourcetype/>
-  <x1:notificationtype/>
- </x0:prop>
-</x0:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendar_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
+ <x0:prop>
+  <x0:getetag/>
+  <x0:resourcetype/>
+  <x1:notificationtype/>
+ </x0:prop>
+</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x1="http://calendarserver.org/ns/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
- <x0:prop>
-  <x1:xmpp-server/>
-  <x1:xmpp-uri/>
-  <x1:getctag/>
-  <x0:displayname/>
-  <x2:calendar-description/>
-  <x3:calendar-color/>
-  <x3:calendar-order/>
-  <x2:supported-calendar-component-set/>
-  <x0:resourcetype/>
-  <x0:owner/>
-  <x2:calendar-free-busy-set/>
-  <x2:schedule-calendar-transp/>
-  <x2:schedule-default-calendar-URL/>
-  <x0:quota-available-bytes/>
-  <x0:quota-used-bytes/>
-  <x2:calendar-timezone/>
-  <x0:current-user-privilege-set/>
-  <x1:source/>
-  <x1:subscribed-strip-alarms/>
-  <x1:subscribed-strip-attachments/>
-  <x1:subscribed-strip-todos/>
-  <x3:refreshrate/>
-  <x1:push-transports/>
-  <x1:pushkey/>
-  <x1:publish-url/>
- </x0:prop>
-</x0:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x1="http://calendarserver.org/ns/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
+ <x0:prop>
+  <x1:xmpp-server/>
+  <x1:xmpp-uri/>
+  <x1:getctag/>
+  <x0:displayname/>
+  <x2:calendar-description/>
+  <x3:calendar-color/>
+  <x3:calendar-order/>
+  <x2:supported-calendar-component-set/>
+  <x0:resourcetype/>
+  <x0:owner/>
+  <x2:calendar-free-busy-set/>
+  <x2:schedule-calendar-transp/>
+  <x2:schedule-default-calendar-URL/>
+  <x0:quota-available-bytes/>
+  <x0:quota-used-bytes/>
+  <x2:calendar-timezone/>
+  <x0:current-user-privilege-set/>
+  <x1:source/>
+  <x1:subscribed-strip-alarms/>
+  <x1:subscribed-strip-attachments/>
+  <x1:subscribed-strip-todos/>
+  <x3:refreshrate/>
+  <x1:push-transports/>
+  <x1:pushkey/>
+  <x1:publish-url/>
+ </x0:prop>
+</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:getetag/>
-    <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/poll_notification_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getetag/>
+    <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,14 +0,0 @@
-BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VFREEBUSY
-UID:%(vfreebusy-uid)s
-DTEND:%(end)s
-%(attendees)sDTSTART:%(start)s
-%(event-mask)sDTSTAMP:%(now)s
-ORGANIZER:%(organizer)s
-SUMMARY:%(summary)s
-END:VFREEBUSY
-END:VCALENDAR

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/post_availability.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(vfreebusy-uid)s
+DTEND:%(end)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
+ORGANIZER:%(organizer)s
+SUMMARY:%(summary)s
+END:VFREEBUSY
+END:VCALENDAR

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_color_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_order_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 5.0.2//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:DAYLIGHT
-TZOFFSETFROM:-0500
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-DTSTART:20070311T020000
-TZNAME:EDT
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:-0400
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-DTSTART:20071104T020000
-TZNAME:EST
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-END:VCALENDAR
-</C:calendar-timezone></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_calendar_timezone_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.2//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+</C:calendar-timezone></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:getetag/>
-  <x0:resourcetype/>
-  <x1:notificationtype/>
- </x0:prop>
-</x0:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_notification_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
+ <x0:prop>
+  <x0:getetag/>
+  <x0:resourcetype/>
+  <x1:notificationtype/>
+ </x0:prop>
+</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:expand-property xmlns:x0="DAV:"><x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property><x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property></x0:expand-property>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_expand.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:expand-property xmlns:x0="DAV:"><x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property><x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property></x0:expand-property>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x2="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:principal-collection-set/>
-  <x1:calendar-home-set/>
-  <x1:calendar-user-address-set/>
-  <x1:schedule-inbox-URL/>
-  <x1:schedule-outbox-URL/>
-  <x2:dropbox-home-URL/>
-  <x2:xmpp-uri/>
-  <x2:notification-URL/>
-  <x0:displayname/>
-  <x0:principal-URL/>
-  <x0:supported-report-set/>
- </x0:prop>
-</x0:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<x0:propfind xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x2="http://calendarserver.org/ns/">
+ <x0:prop>
+  <x0:principal-collection-set/>
+  <x1:calendar-home-set/>
+  <x1:calendar-user-address-set/>
+  <x1:schedule-inbox-URL/>
+  <x1:schedule-outbox-URL/>
+  <x2:dropbox-home-URL/>
+  <x2:xmpp-uri/>
+  <x2:notification-URL/>
+  <x0:displayname/>
+  <x0:principal-URL/>
+  <x0:supported-report-set/>
+ </x0:prop>
+</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-search-property-set xmlns:x0="DAV:"/>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-search-property-set xmlns:x0="DAV:"/>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/startup_well_known.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_6/user_list_principal_property_search.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/Profile	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,64 +0,0 @@
-** Initial account setup
-
-PROPFIND well-known unauthorized	- startup_well_known.request
-PROPFIND / unauthorized	            - startup_well_known.request
-HEAD well-known unauthorized
-PROPFIND / unauthorized	            - startup_well_known.request
-HEAD / unauthorized
-PROPFIND / unauthorized				- startup_well_known.request
-PROPFIND /calendar/dav/user01/user/ unauthorized	- startup_principal_propfind_initial.request
-PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind_initial.request
-PROPFIND /principals/users/user01/					- startup_principal_propfind_initial.request
-
-PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind.request
-PROPFIND /principals/users/user01/					- startup_principal_propfind.request
-OPTIONS /principals/__uids__/user01/
-
-PROPFIND /principals/users/user01/		            - startup_principal_propfind.request
-OPTIONS /principals/__uids__/user01/
-
-REPORT /principals/ unauthorized					- startup_principals_report.request
-REPORT /principals/									- startup_principals_report.request
-
-PROPFIND /calendars/__uids__/user01/ unauthorized	- poll_calendarhome_propfind.request
-PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
-
-PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_color_proppatch.request
-PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_order_proppatch.request
-PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_timezone_proppatch.request
-
-PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_color_proppatch.request
-PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_order_proppatch.request
-PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_timezone_proppatch.request
-
-PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind_d1.request
-
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind_d1.request
-
-PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind_d1.request
-
-PROPFIND /calendars/__uids__/user01/notification/	- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/notification/	- poll_notification_propfind_d1.request
-
-REPORT /principals/__uids__/user01/					- startup_principal_expand.request
-
-	** for each delegate
-	PROPFIND /principals/__uids__/resource10/		- startup_delegate_principal_propfind.request
-	OPTIONS /principals/__uids__/user01/
-	REPORT /principals/								- startup_principals_report.request
-	
-** Polling - with sync
-PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
-REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_sync.request
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_multiget.request
-	
-** Polling - without sync
-PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind_d1.request
-REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_multiget.request
-PROPFIND /calendars/__uids__/user01/notification/	- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/notification/	- poll_notification_propfind_d1.request

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/Profile)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/Profile	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,64 @@
+** Initial account setup
+
+PROPFIND well-known unauthorized	- startup_well_known.request
+PROPFIND / unauthorized	            - startup_well_known.request
+HEAD well-known unauthorized
+PROPFIND / unauthorized	            - startup_well_known.request
+HEAD / unauthorized
+PROPFIND / unauthorized				- startup_well_known.request
+PROPFIND /calendar/dav/user01/user/ unauthorized	- startup_principal_propfind_initial.request
+PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind_initial.request
+PROPFIND /principals/users/user01/					- startup_principal_propfind_initial.request
+
+PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind.request
+PROPFIND /principals/users/user01/					- startup_principal_propfind.request
+OPTIONS /principals/__uids__/user01/
+
+PROPFIND /principals/users/user01/		            - startup_principal_propfind.request
+OPTIONS /principals/__uids__/user01/
+
+REPORT /principals/ unauthorized					- startup_principals_report.request
+REPORT /principals/									- startup_principals_report.request
+
+PROPFIND /calendars/__uids__/user01/ unauthorized	- poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_order_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_timezone_proppatch.request
+
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_order_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_timezone_proppatch.request
+
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind_d1.request
+
+PROPFIND /calendars/__uids__/user01/notification/	- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/notification/	- poll_notification_propfind_d1.request
+
+REPORT /principals/__uids__/user01/					- startup_principal_expand.request
+
+	** for each delegate
+	PROPFIND /principals/__uids__/resource10/		- startup_delegate_principal_propfind.request
+	OPTIONS /principals/__uids__/user01/
+	REPORT /principals/								- startup_principals_report.request
+	
+** Polling - with sync
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_sync.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_multiget.request
+	
+** Polling - without sync
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind_d1.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_multiget.request
+PROPFIND /calendars/__uids__/user01/notification/	- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/notification/	- poll_notification_propfind_d1.request

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav">
-  <A:prop xmlns:A="DAV:">
-    <A:getetag/>
-    <C:calendar-data/>
-    <C:schedule-tag/>
-  </A:prop>
-%(hrefs)s
-</C:calendar-multiget>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<C:calendar-multiget xmlns:C="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <C:calendar-data/>
+    <C:schedule-tag/>
+  </A:prop>
+%(hrefs)s
+</C:calendar-multiget>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1 +0,0 @@
-  <A:href xmlns:A="DAV:">%(href)s</A:href>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_multiget_hrefs.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1 @@
+  <A:href xmlns:A="DAV:">%(href)s</A:href>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
-    <A:sync-token/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:getcontenttype/>
-    <A:getetag/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getcontenttype/>
+    <A:getetag/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:sync-collection xmlns:A="DAV:">
-  <A:sync-token>%(sync-token)s</A:sync-token>
-  <A:prop>
-    <A:getetag/>
-    <A:getcontenttype/>
-  </A:prop>
-</A:sync-collection>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendar_sync.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:sync-collection xmlns:A="DAV:">
+  <A:sync-token>%(sync-token)s</A:sync-token>
+  <A:prop>
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+</A:sync-collection>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:add-member/>
-    <B:allowed-sharing-modes xmlns:B="http://calendarserver.org/ns/"/>
-    <D:bulk-requests xmlns:D="http://me.com/_namespace/"/>
-    <E:calendar-color xmlns:E="http://apple.com/ns/ical/"/>
-    <C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <C:calendar-free-busy-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <E:calendar-order xmlns:E="http://apple.com/ns/ical/"/>
-    <C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <A:current-user-privilege-set/>
-    <A:displayname/>
-    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
-    <F:max-image-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
-    <F:max-resource-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
-    <B:me-card xmlns:B="http://calendarserver.org/ns/"/>
-    <A:owner/>
-    <B:publish-url xmlns:B="http://calendarserver.org/ns/"/>
-    <B:push-transports xmlns:B="http://calendarserver.org/ns/"/>
-    <B:pushkey xmlns:B="http://calendarserver.org/ns/"/>
-    <A:quota-available-bytes/>
-    <A:quota-used-bytes/>
-    <E:refreshrate xmlns:E="http://apple.com/ns/ical/"/>
-    <A:resource-id/>
-    <A:resourcetype/>
-    <C:schedule-calendar-transp xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <C:schedule-default-calendar-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <B:source xmlns:B="http://calendarserver.org/ns/"/>
-    <B:subscribed-strip-alarms xmlns:B="http://calendarserver.org/ns/"/>
-    <B:subscribed-strip-attachments xmlns:B="http://calendarserver.org/ns/"/>
-    <B:subscribed-strip-todos xmlns:B="http://calendarserver.org/ns/"/>
-    <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <A:supported-report-set/>
-    <A:sync-token/>
-    <B:xmpp-server xmlns:B="http://calendarserver.org/ns/"/>
-    <B:xmpp-uri xmlns:B="http://calendarserver.org/ns/"/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:add-member/>
+    <B:allowed-sharing-modes xmlns:B="http://calendarserver.org/ns/"/>
+    <D:bulk-requests xmlns:D="http://me.com/_namespace/"/>
+    <E:calendar-color xmlns:E="http://apple.com/ns/ical/"/>
+    <C:calendar-description xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:calendar-free-busy-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <E:calendar-order xmlns:E="http://apple.com/ns/ical/"/>
+    <C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-privilege-set/>
+    <A:displayname/>
+    <B:getctag xmlns:B="http://calendarserver.org/ns/"/>
+    <F:max-image-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
+    <F:max-resource-size xmlns:F="urn:ietf:params:xml:ns:carddav"/>
+    <B:me-card xmlns:B="http://calendarserver.org/ns/"/>
+    <A:owner/>
+    <B:publish-url xmlns:B="http://calendarserver.org/ns/"/>
+    <B:push-transports xmlns:B="http://calendarserver.org/ns/"/>
+    <B:pushkey xmlns:B="http://calendarserver.org/ns/"/>
+    <A:quota-available-bytes/>
+    <A:quota-used-bytes/>
+    <E:refreshrate xmlns:E="http://apple.com/ns/ical/"/>
+    <A:resource-id/>
+    <A:resourcetype/>
+    <C:schedule-calendar-transp xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:schedule-default-calendar-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <B:source xmlns:B="http://calendarserver.org/ns/"/>
+    <B:subscribed-strip-alarms xmlns:B="http://calendarserver.org/ns/"/>
+    <B:subscribed-strip-attachments xmlns:B="http://calendarserver.org/ns/"/>
+    <B:subscribed-strip-todos xmlns:B="http://calendarserver.org/ns/"/>
+    <C:supported-calendar-component-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:sync-token/>
+    <B:xmpp-server xmlns:B="http://calendarserver.org/ns/"/>
+    <B:xmpp-uri xmlns:B="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:getetag/>
-    <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/poll_notification_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getetag/>
+    <B:notificationtype xmlns:B="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,14 +0,0 @@
-BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VFREEBUSY
-UID:%(vfreebusy-uid)s
-DTEND:%(end)s
-%(attendees)sDTSTART:%(start)s
-%(event-mask)sDTSTAMP:%(now)s
-ORGANIZER:%(organizer)s
-SUMMARY:%(summary)s
-END:VFREEBUSY
-END:VCALENDAR

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/post_availability.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,14 @@
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(vfreebusy-uid)s
+DTEND:%(end)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
+ORGANIZER:%(organizer)s
+SUMMARY:%(summary)s
+END:VFREEBUSY
+END:VCALENDAR

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_color_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-color xmlns:E="http://apple.com/ns/ical/">#882F00FF</E:calendar-color></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_order_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><E:calendar-order xmlns:E="http://apple.com/ns/ical/">1</E:calendar-order></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//Apple Inc.//iCal 5.0.2//EN
-CALSCALE:GREGORIAN
-BEGIN:VTIMEZONE
-TZID:America/New_York
-BEGIN:DAYLIGHT
-TZOFFSETFROM:-0500
-RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
-DTSTART:20070311T020000
-TZNAME:EDT
-TZOFFSETTO:-0400
-END:DAYLIGHT
-BEGIN:STANDARD
-TZOFFSETFROM:-0400
-RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
-DTSTART:20071104T020000
-TZNAME:EST
-TZOFFSETTO:-0500
-END:STANDARD
-END:VTIMEZONE
-END:VCALENDAR
-</C:calendar-timezone></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_calendar_timezone_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><C:calendar-timezone xmlns:C="urn:ietf:params:xml:ns:caldav">BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 5.0.2//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_York
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0500
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:EDT
+TZOFFSETTO:-0400
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0400
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:EST
+TZOFFSETTO:-0500
+END:STANDARD
+END:VTIMEZONE
+END:VCALENDAR
+</C:calendar-timezone></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
-    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <A:current-user-principal/>
-    <A:displayname/>
-    <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
-    <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
-    <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
-    <A:principal-collection-set/>
-    <A:principal-URL/>
-    <A:resource-id/>
-    <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <A:supported-report-set/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_delegate_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-principal/>
+    <A:displayname/>
+    <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+    <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <A:principal-collection-set/>
+    <A:principal-URL/>
+    <A:resource-id/>
+    <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,13 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:expand-property xmlns:A="DAV:">
-  <A:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
-    <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
-    <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
-    <A:property name="displayname" namespace="DAV:"/>
-  </A:property>
-  <A:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
-    <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
-    <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
-    <A:property name="displayname" namespace="DAV:"/>
-  </A:property>
-</A:expand-property>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_expand.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:expand-property xmlns:A="DAV:">
+  <A:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/">
+    <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+    <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+    <A:property name="displayname" namespace="DAV:"/>
+  </A:property>
+  <A:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/">
+    <A:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/>
+    <A:property name="email-address-set" namespace="http://calendarserver.org/ns/"/>
+    <A:property name="displayname" namespace="DAV:"/>
+  </A:property>
+</A:expand-property>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
-    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <A:current-user-principal/>
-    <A:displayname/>
-    <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
-    <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
-    <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
-    <A:principal-collection-set/>
-    <A:principal-URL/>
-    <A:resource-id/>
-    <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
-    <A:supported-report-set/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <B:allowed-calendar-component-set xmlns:B="http://calendarserver.org/ns/"/>
+    <C:calendar-home-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:calendar-user-address-set xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-principal/>
+    <A:displayname/>
+    <B:dropbox-home-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <B:email-address-set xmlns:B="http://calendarserver.org/ns/"/>
+    <B:notification-URL xmlns:B="http://calendarserver.org/ns/"/>
+    <A:principal-collection-set/>
+    <A:principal-URL/>
+    <A:resource-id/>
+    <C:schedule-inbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <C:schedule-outbox-URL xmlns:C="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-search-property-set xmlns:x0="DAV:"/>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-search-property-set xmlns:x0="DAV:"/>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/startup_well_known.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/OS_X_10_7/user_list_principal_property_search.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/Profile	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,44 +0,0 @@
-** Initial account setup
-
-PROPFIND well-known unauthorized	- startup_well_known.request
-PROPFIND / unauthorized	            - startup_well_known.request
-PROPFIND /principals/ unauthorized	            - startup_principal_propfind_initial.request
-PROPFIND /principals/ authorized	            - startup_principal_propfind_initial.request
-
-OPTIONS /principals/__uids__/user01/ unauthorized
-OPTIONS /principals/__uids__/user01/ authorized
-
-PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind.request
-PROPFIND /principals/users/user01/					- startup_principal_propfind.request
-
-REPORT /principals/ unauthorized					- startup_principals_report.request
-REPORT /principals/									- startup_principals_report.request
-
-PROPFIND /calendars/__uids__/user01/ unauthorized	- poll_calendarhome_propfind.request
-PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
-
-PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_color_proppatch.request
-PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_order_proppatch.request
-
-PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_color_proppatch.request
-PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_order_proppatch.request
-
-PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_vtodo_query.request
-
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_vevent_tr_query.request
-
-PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind.request
-PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind_d1.request
-	
-** Polling - without sync
-PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
-
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
-REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_propfind_d1.request
-REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_multiget.request
-
-PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
-REPORT /calendars/__uids__/user01/tasks/			- poll_calendar_vevent_tr_query.request
-REPORT /calendars/__uids__/user01/tasks/			- poll_calendar_vtodo_query.request

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/Profile)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/Profile	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,44 @@
+** Initial account setup
+
+PROPFIND well-known unauthorized	- startup_well_known.request
+PROPFIND / unauthorized	            - startup_well_known.request
+PROPFIND /principals/ unauthorized	            - startup_principal_propfind_initial.request
+PROPFIND /principals/ authorized	            - startup_principal_propfind_initial.request
+
+OPTIONS /principals/__uids__/user01/ unauthorized
+OPTIONS /principals/__uids__/user01/ authorized
+
+PROPFIND /principals/users/user01/ unauthorized		- startup_principal_propfind.request
+PROPFIND /principals/users/user01/					- startup_principal_propfind.request
+
+REPORT /principals/ unauthorized					- startup_principals_report.request
+REPORT /principals/									- startup_principals_report.request
+
+PROPFIND /calendars/__uids__/user01/ unauthorized	- poll_calendarhome_propfind.request
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/tasks/ 		- startup_calendar_order_proppatch.request
+
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_color_proppatch.request
+PROPPATCH /calendars/__uids__/user01/calendar/ 		- startup_calendar_order_proppatch.request
+
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/tasks/			- poll_calendar_vtodo_query.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_vevent_tr_query.request
+
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind.request
+PROPFIND /calendars/__uids__/user01/inbox/			- poll_calendar_propfind_d1.request
+	
+** Polling - without sync
+PROPFIND /calendars/__uids__/user01/				- poll_calendarhome_propfind.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_propfind_d1.request
+REPORT /calendars/__uids__/user01/calendar/			- poll_calendar_multiget.request
+
+PROPFIND /calendars/__uids__/user01/calendar/		- poll_calendar_propfind.request
+REPORT /calendars/__uids__/user01/tasks/			- poll_calendar_vevent_tr_query.request
+REPORT /calendars/__uids__/user01/tasks/			- poll_calendar_vtodo_query.request

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<G:calendar-multiget xmlns:G="urn:ietf:params:xml:ns:caldav">
-  <A:prop xmlns:A="DAV:">
-    <A:getetag/>
-    <G:calendar-data/>
-    <G:schedule-tag/>
-  </A:prop>
-%(hrefs)s
-</G:calendar-multiget>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-multiget xmlns:G="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <G:calendar-data/>
+    <G:schedule-tag/>
+  </A:prop>
+%(hrefs)s
+</G:calendar-multiget>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1 +0,0 @@
-  <A:href xmlns:A="DAV:">%(href)s</A:href>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_multiget_hrefs.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1 @@
+  <A:href xmlns:A="DAV:">%(href)s</A:href>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
-    <A:sync-token/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+    <A:sync-token/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:getcontenttype/>
-    <A:getetag/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_propfind_d1.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:getcontenttype/>
+    <A:getetag/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,14 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
-  <A:prop xmlns:A="DAV:">
-    <A:getetag/>
-    <A:getcontenttype/>
-  </A:prop>
-  <G:filter>
-    <G:comp-filter name="VCALENDAR">
-      <G:comp-filter name="VEVENT">
-        <G:time-range start="%(start-date)sT000000Z"/>
-      </G:comp-filter>
-    </G:comp-filter>
-  </G:filter>
-</G:calendar-query>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vevent_tr_query.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+  <G:filter>
+    <G:comp-filter name="VCALENDAR">
+      <G:comp-filter name="VEVENT">
+        <G:time-range start="%(start-date)sT000000Z"/>
+      </G:comp-filter>
+    </G:comp-filter>
+  </G:filter>
+</G:calendar-query>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,12 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
-  <A:prop xmlns:A="DAV:">
-    <A:getetag/>
-    <A:getcontenttype/>
-  </A:prop>
-  <G:filter>
-    <G:comp-filter name="VCALENDAR">
-      <G:comp-filter name="VTODO"/>
-    </G:comp-filter>
-  </G:filter>
-</G:calendar-query>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendar_vtodo_query.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<G:calendar-query xmlns:G="urn:ietf:params:xml:ns:caldav">
+  <A:prop xmlns:A="DAV:">
+    <A:getetag/>
+    <A:getcontenttype/>
+  </A:prop>
+  <G:filter>
+    <G:comp-filter name="VCALENDAR">
+      <G:comp-filter name="VTODO"/>
+    </G:comp-filter>
+  </G:filter>
+</G:calendar-query>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:add-member/>
-    <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
-    <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
-    <H:calendar-color xmlns:H="http://apple.com/ns/ical/"/>
-    <G:calendar-description xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <G:calendar-free-busy-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <H:calendar-order xmlns:H="http://apple.com/ns/ical/"/>
-    <G:calendar-timezone xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <A:current-user-privilege-set/>
-    <A:displayname/>
-    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
-    <B:max-image-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
-    <B:max-resource-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
-    <C:me-card xmlns:C="http://calendarserver.org/ns/"/>
-    <A:owner/>
-    <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
-    <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
-    <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
-    <A:quota-available-bytes/>
-    <A:quota-used-bytes/>
-    <H:refreshrate xmlns:H="http://apple.com/ns/ical/"/>
-    <A:resource-id/>
-    <A:resourcetype/>
-    <G:schedule-calendar-transp xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <G:schedule-default-calendar-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <C:source xmlns:C="http://calendarserver.org/ns/"/>
-    <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
-    <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
-    <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
-    <G:supported-calendar-component-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <A:supported-report-set/>
-    <A:sync-token/>
-    <C:xmpp-server xmlns:C="http://calendarserver.org/ns/"/>
-    <C:xmpp-uri xmlns:C="http://calendarserver.org/ns/"/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/poll_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:add-member/>
+    <C:allowed-sharing-modes xmlns:C="http://calendarserver.org/ns/"/>
+    <E:bulk-requests xmlns:E="http://me.com/_namespace/"/>
+    <H:calendar-color xmlns:H="http://apple.com/ns/ical/"/>
+    <G:calendar-description xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:calendar-free-busy-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <H:calendar-order xmlns:H="http://apple.com/ns/ical/"/>
+    <G:calendar-timezone xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-privilege-set/>
+    <A:displayname/>
+    <C:getctag xmlns:C="http://calendarserver.org/ns/"/>
+    <B:max-image-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
+    <B:max-resource-size xmlns:B="urn:ietf:params:xml:ns:carddav"/>
+    <C:me-card xmlns:C="http://calendarserver.org/ns/"/>
+    <A:owner/>
+    <C:publish-url xmlns:C="http://calendarserver.org/ns/"/>
+    <C:push-transports xmlns:C="http://calendarserver.org/ns/"/>
+    <C:pushkey xmlns:C="http://calendarserver.org/ns/"/>
+    <A:quota-available-bytes/>
+    <A:quota-used-bytes/>
+    <H:refreshrate xmlns:H="http://apple.com/ns/ical/"/>
+    <A:resource-id/>
+    <A:resourcetype/>
+    <G:schedule-calendar-transp xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:schedule-default-calendar-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <C:source xmlns:C="http://calendarserver.org/ns/"/>
+    <C:subscribed-strip-alarms xmlns:C="http://calendarserver.org/ns/"/>
+    <C:subscribed-strip-attachments xmlns:C="http://calendarserver.org/ns/"/>
+    <C:subscribed-strip-todos xmlns:C="http://calendarserver.org/ns/"/>
+    <G:supported-calendar-component-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+    <A:sync-token/>
+    <C:xmpp-server xmlns:C="http://calendarserver.org/ns/"/>
+    <C:xmpp-uri xmlns:C="http://calendarserver.org/ns/"/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-color xmlns:H="http://apple.com/ns/ical/">#711A76</H:calendar-color></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_color_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-color xmlns:H="http://apple.com/ns/ical/">#711A76</H:calendar-color></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-order xmlns:H="http://apple.com/ns/ical/">0</H:calendar-order></A:prop></A:set></A:propertyupdate>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_calendar_order_proppatch.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propertyupdate xmlns:A="DAV:"><A:set><A:prop><H:calendar-order xmlns:H="http://apple.com/ns/ical/">0</H:calendar-order></A:prop></A:set></A:propertyupdate>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <C:allowed-calendar-component-set xmlns:C="http://calendarserver.org/ns/"/>
-    <G:calendar-home-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <G:calendar-user-address-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <A:current-user-principal/>
-    <A:displayname/>
-    <C:dropbox-home-URL xmlns:C="http://calendarserver.org/ns/"/>
-    <C:email-address-set xmlns:C="http://calendarserver.org/ns/"/>
-    <C:notification-URL xmlns:C="http://calendarserver.org/ns/"/>
-    <A:principal-collection-set/>
-    <A:principal-URL/>
-    <A:resource-id/>
-    <G:schedule-inbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <G:schedule-outbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
-    <A:supported-report-set/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <C:allowed-calendar-component-set xmlns:C="http://calendarserver.org/ns/"/>
+    <G:calendar-home-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:calendar-user-address-set xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:current-user-principal/>
+    <A:displayname/>
+    <C:dropbox-home-URL xmlns:C="http://calendarserver.org/ns/"/>
+    <C:email-address-set xmlns:C="http://calendarserver.org/ns/"/>
+    <C:notification-URL xmlns:C="http://calendarserver.org/ns/"/>
+    <A:principal-collection-set/>
+    <A:principal-URL/>
+    <A:resource-id/>
+    <G:schedule-inbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <G:schedule-outbox-URL xmlns:G="urn:ietf:params:xml:ns:caldav"/>
+    <A:supported-report-set/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:principal-search-property-set xmlns:A="DAV:"/>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:principal-search-property-set xmlns:A="DAV:"/>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request
===================================================================
--- CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request	2012-04-13 18:48:13 UTC (rev 9105)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/iOS_5/startup_well_known.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<A:propfind xmlns:A="DAV:">
+  <A:prop>
+    <A:current-user-principal/>
+    <A:principal-URL/>
+    <A:resourcetype/>
+  </A:prop>
+</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_propfind.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_propfind.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:getetag/>
-  <x0:resourcetype/>
-  <x1:notificationtype/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,9 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:calendar-multiget xmlns:x0="urn:ietf:params:xml:ns:caldav" xmlns:x1="DAV:">
-  <x1:prop>
-    <x1:getetag/>
-    <x0:calendar-data/>
-    <x0:schedule-tag/>
-  </x1:prop>
-%(hrefs)s
-</x0:calendar-multiget>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report_href.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report_href.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_calendar_report_href.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1 +0,0 @@
-  <x1:href>%(href)s</x1:href>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_post_availability.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_post_availability.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_post_availability.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,14 +0,0 @@
-BEGIN:VCALENDAR
-CALSCALE:GREGORIAN
-VERSION:2.0
-METHOD:REQUEST
-PRODID:-//Apple Inc.//iCal 4.0.3//EN
-BEGIN:VFREEBUSY
-UID:%(vfreebusy-uid)s
-DTEND:%(end)s
-%(attendees)sDTSTART:%(start)s
-%(event-mask)sDTSTAMP:%(now)s
-ORGANIZER:%(organizer)s
-SUMMARY:%(summary)s
-END:VFREEBUSY
-END:VCALENDAR

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_calendarhome_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x3="http://apple.com/ns/ical/" xmlns:x1="http://calendarserver.org/ns/" xmlns:x2="urn:ietf:params:xml:ns:caldav">
- <x0:prop>
-  <x1:xmpp-server/>
-  <x1:xmpp-uri/>
-  <x1:getctag/>
-  <x0:displayname/>
-  <x2:calendar-description/>
-  <x3:calendar-color/>
-  <x3:calendar-order/>
-  <x2:supported-calendar-component-set/>
-  <x0:resourcetype/>
-  <x0:owner/>
-  <x2:calendar-free-busy-set/>
-  <x2:schedule-calendar-transp/>
-  <x2:schedule-default-calendar-URL/>
-  <x0:quota-available-bytes/>
-  <x0:quota-used-bytes/>
-  <x2:calendar-timezone/>
-  <x0:current-user-privilege-set/>
-  <x1:source/>
-  <x1:subscribed-strip-alarms/>
-  <x1:subscribed-strip-attachments/>
-  <x1:subscribed-strip-todos/>
-  <x3:refreshrate/>
-  <x1:push-transports/>
-  <x1:pushkey/>
-  <x1:publish-url/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_notification_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:getetag/>
-  <x0:resourcetype/>
-  <x1:notificationtype/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<x0:propfind xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x2="http://calendarserver.org/ns/">
- <x0:prop>
-  <x0:principal-collection-set/>
-  <x1:calendar-home-set/>
-  <x1:calendar-user-address-set/>
-  <x1:schedule-inbox-URL/>
-  <x1:schedule-outbox-URL/>
-  <x2:dropbox-home-URL/>
-  <x2:xmpp-uri/>
-  <x2:notification-URL/>
-  <x0:displayname/>
-  <x0:principal-URL/>
-  <x0:supported-report-set/>
- </x0:prop>
-</x0:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_propfind_initial.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,8 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<A:propfind xmlns:A="DAV:">
-  <A:prop>
-    <A:current-user-principal/>
-    <A:principal-URL/>
-    <A:resourcetype/>
-  </A:prop>
-</A:propfind>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_report.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_report.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principal_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:expand-property xmlns:x0="DAV:"><x0:property name="calendar-proxy-write-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property><x0:property name="calendar-proxy-read-for" namespace="http://calendarserver.org/ns/"><x0:property name="displayname"/><x0:property name="principal-URL"/><x0:property name="calendar-user-address-set" namespace="urn:ietf:params:xml:ns:caldav"/></x0:property></x0:expand-property>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principals_report.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principals_report.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_startup_principals_report.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-search-property-set xmlns:x0="DAV:"/>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/request-data/sl_user_list_principal_property_search.request	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,2 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<x0:principal-property-search xmlns:x2="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:" xmlns:x1="http://calendarserver.org/ns/" test="anyof"><x0:property-search><x0:prop><x0:displayname/></x0:prop><x0:match match-type="starts-with">%(displayname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:email-address-set/></x0:prop><x0:match match-type="starts-with">%(email)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:first-name/></x0:prop><x0:match match-type="starts-with">%(firstname)s</x0:match></x0:property-search><x0:property-search><x0:prop><x1:last-name/></x0:prop><x0:match match-type="starts-with">%(lastname)s</x0:match></x0:property-search><x0:prop><x1:email-address-set/><x2:calendar-user-address-set/><x2:calendar-user-type/><x0:displayname/><x1:last-name/><x1:first-name/><x1:record-type/><x0:principal-URL/></x0:prop></x0:principal-property-search>

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/sim.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/sim.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,6 +1,6 @@
 # -*- test-case-name: contrib.performance.loadtest.test_sim -*-
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 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.
@@ -16,12 +16,12 @@
 #
 ##
 
+from collections import namedtuple
 from os import environ
+from plistlib import readPlist
+from random import Random
+from sys import argv, stdout
 from xml.parsers.expat import ExpatError
-from sys import argv, stdout
-from random import Random
-from plistlib import readPlist
-from collections import namedtuple
 
 from twisted.python import context
 from twisted.python.filepath import FilePath
@@ -32,15 +32,17 @@
 from twisted.application.service import Service
 from twisted.application.service import MultiService
 
+from twisted.internet.defer import Deferred
+from twisted.internet.defer import gatherResults
+from twisted.internet.defer import inlineCallbacks
 from twisted.internet.protocol import ProcessProtocol
 
-from contrib.performance.loadtest.ical import SnowLeopard
+from contrib.performance.loadtest.ical import OS_X_10_6
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
 from contrib.performance.loadtest.population import (
     Populator, ProfileType, ClientType, PopulationParameters, SmoothRampUp,
     CalendarClientSimulator)
-from twisted.internet.defer import Deferred
-from twisted.internet.defer import gatherResults
+from contrib.performance.loadtest.webadmin import LoadSimAdminResource
 
 
 class _DirectoryRecord(object):
@@ -176,6 +178,8 @@
 Arrival = namedtuple('Arrival', 'factory parameters')
 
 
+from twisted.web import server
+
 class LoadSimulator(object):
     """
     A L{LoadSimulator} simulates some configuration of calendar
@@ -189,15 +193,17 @@
         user information about the accounts on the server being put
         under load.
     """
-    def __init__(self, server, arrival, parameters, observers=None,
+    def __init__(self, server, webadminPort, arrival, parameters, observers=None,
                  records=None, reactor=None, runtime=None, workers=None,
                  configTemplate=None, workerID=None, workerCount=1):
         if reactor is None:
             from twisted.internet import reactor
         self.server = server
+        self.webadminPort = webadminPort
         self.arrival = arrival
         self.parameters = parameters
         self.observers = observers
+        self.reporter = None
         self.records = records
         self.reactor = LagTrackingReactor(reactor)
         self.runtime = runtime
@@ -235,9 +241,15 @@
             workerCount = config.get("workerCount", 1)
             configTemplate = None
             server = 'http://127.0.0.1:8008/'
+            webadminPort = None
+
             if 'server' in config:
                 server = config['server']
 
+            if 'webadmin' in config:
+                if config['webadmin']['enabled']:
+                    webadminPort = config['webadmin']['HTTPPort']
+
             if 'arrival' in config:
                 arrival = Arrival(
                     namedAny(config['arrival']['factory']), 
@@ -260,11 +272,12 @@
                              for profile in clientConfig["profiles"]]))
             if not parameters.clients:
                 parameters.addClient(1,
-                                     ClientType(SnowLeopard, {},
+                                     ClientType(OS_X_10_6, {},
                                                 [Eventer, Inviter, Accepter]))
         else:
             # Manager / observer process.
             server = ''
+            webadminPort = None
             arrival = None
             parameters = None
             workerID = 0
@@ -283,7 +296,7 @@
             records.extend(namedAny(loader)(**params))
             output.write("Loaded {0} accounts.\n".format(len(records)))
 
-        return cls(server, arrival, parameters, observers=observers,
+        return cls(server, webadminPort, arrival, parameters, observers=observers,
                    records=records, runtime=runtime, reactor=reactor,
                    workers=workers, configTemplate=configTemplate,
                    workerID=workerID, workerCount=workerCount)
@@ -351,26 +364,39 @@
 
 
     def attachServices(self, output):
-        ms = MultiService()
+        self.ms = MultiService()
         for svcclass in self.serviceClasses():
-            svcclass(self, output).setServiceParent(ms)
-        attachService(self.reactor, ms)
+            svcclass(self, output).setServiceParent(self.ms)
+        attachService(self.reactor, self, self.ms)
 
 
     def run(self, output=stdout):
         self.attachServices(output)
         if self.runtime is not None:
             self.reactor.callLater(self.runtime, self.reactor.stop)
+        if self.webadminPort:
+            self.reactor.listenTCP(self.webadminPort, server.Site(LoadSimAdminResource(self)))
         self.reactor.run()
 
 
-def attachService(reactor, service):
+    def stop(self):
+        if self.ms.running:
+            self.ms.stopService()
+            self.reactor.callLater(5, self.reactor.stop)
+
+
+    def shutdown(self):
+        if self.ms.running:
+            return self.ms.stopService()
+
+
+def attachService(reactor, loadsim, service):
     """
     Attach a given L{IService} provider to the given L{IReactorCore}; cause it
     to be started when the reactor starts, and stopped when the reactor stops.
     """
     reactor.callWhenRunning(service.startService)
-    reactor.addSystemEventTrigger('before', 'shutdown', service.stopService)
+    reactor.addSystemEventTrigger('before', 'shutdown', loadsim.shutdown)
 
 
 
@@ -402,7 +428,7 @@
 
 
     def stopService(self):
-        super(ObserverService, self).startService()
+        super(ObserverService, self).stopService()
         for obs in self.loadsim.observers:
             removeObserver(obs.observe)
 
@@ -421,9 +447,10 @@
         arrivalPolicy.run(self.clientsim)
 
 
+    @inlineCallbacks
     def stopService(self):
-        super(SimulatorService, self).stopService()
-        return self.clientsim.stop()
+        yield super(SimulatorService, self).stopService()
+        yield self.clientsim.stop()
 
 
 
@@ -433,24 +460,38 @@
     simulator when it is stopped.
     """
 
+    def startService(self):
+        """
+        Start observing.
+        """
+        super(ReporterService, self).startService()
+        self.loadsim.reporter = self
+
+
     def stopService(self):
         """
         Emit the report to the specified output file.
         """
         super(ReporterService, self).stopService()
+        self.generateReport(self.output)
+
+
+    def generateReport(self, output):
+        """
+        Emit the report to the specified output file.
+        """
         failures = []
         for obs in self.loadsim.observers:
-            obs.report()
+            obs.report(output)
             failures.extend(obs.failures())
         if failures:
-            self.output.write('FAIL\n')
-            self.output.write('\n'.join(failures))
-            self.output.write('\n')
+            output.write('\n*** FAIL\n')
+            output.write('\n'.join(failures))
+            output.write('\n')
         else:
-            self.output.write('PASS\n')
+            output.write('\n*** PASS\n')
 
 
-
 class ProcessProtocolBridge(ProcessProtocol):
 
     def __init__(self, spawner, proto):

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_ical.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_ical.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2012 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.
@@ -28,7 +28,7 @@
 from caldavclientlibrary.protocol.caldav.definitions import caldavxml
 from caldavclientlibrary.protocol.caldav.definitions import csxml
 
-from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, SnowLeopard
+from contrib.performance.loadtest.ical import XMPPPush, Event, Calendar, OS_X_10_6
 from contrib.performance.loadtest.sim import _DirectoryRecord
 from contrib.performance.httpclient import MemoryConsumer, StringProducer
 from twistedcaldav.ical import Component
@@ -1146,20 +1146,20 @@
 
 
 
-class SnowLeopardMixin:
+class OS_X_10_6Mixin:
     """
-    Mixin for L{TestCase}s for L{SnowLeopard}.
+    Mixin for L{TestCase}s for L{OS_X_10_6}.
     """
     def setUp(self):
         TimezoneCache.create()
         self.record = _DirectoryRecord(
             u"user91", u"user91", u"User 91", u"user91 at example.org")
-        self.client = SnowLeopard(None, "http://127.0.0.1/", self.record, None)
+        self.client = OS_X_10_6(None, "http://127.0.0.1/", self.record, None)
 
 
     def interceptRequests(self):
         requests = []
-        def request(*args):
+        def request(*args, **kwargs):
             result = Deferred()
             requests.append((result, args))
             return result
@@ -1168,9 +1168,9 @@
 
 
 
-class SnowLeopardTests(SnowLeopardMixin, TestCase):
+class OS_X_10_6Tests(OS_X_10_6Mixin, TestCase):
     """
-    Tests for L{SnowLeopard}.
+    Tests for L{OS_X_10_6}.
     """
     def test_parsePrincipalPROPFINDResponse(self):
         """
@@ -1211,52 +1211,39 @@
 
     def test_extractCalendars(self):
         """
-        L{SnowLeopard._extractCalendars} accepts a calendar home
+        L{OS_X_10_6._extractCalendars} accepts a calendar home
         PROPFIND response body and returns a list of calendar objects
         constructed from the data extracted from the response.
         """
         home = "/calendars/__uids__/user01/"
         calendars = self.client._extractCalendars(
-            CALENDAR_HOME_PROPFIND_RESPONSE, home)
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE), home)
         calendars.sort(key=lambda cal: cal.resourceType)
-        dropbox, notification, calendar, inbox, outbox = calendars
+        calendar, inbox = calendars
 
-        self.assertEquals(dropbox.resourceType, csxml.dropbox_home)
-        self.assertEquals(dropbox.name, None)
-        self.assertEquals(dropbox.url, "/calendars/__uids__/user01/dropbox/")
-        self.assertEquals(dropbox.ctag, None)
-
-        self.assertEquals(notification.resourceType, csxml.notification)
-        self.assertEquals(notification.name, "notification")
-        self.assertEquals(notification.url, "/calendars/__uids__/user01/notification/")
-        self.assertEquals(notification.ctag, None)
-
         self.assertEquals(calendar.resourceType, caldavxml.calendar)
         self.assertEquals(calendar.name, "calendar")
         self.assertEquals(calendar.url, "/calendars/__uids__/user01/calendar/")
-        self.assertEquals(calendar.ctag, "c2696540-4c4c-4a31-adaf-c99630776828#3")
+        self.assertEquals(calendar.changeToken, "c2696540-4c4c-4a31-adaf-c99630776828#3")
 
         self.assertEquals(inbox.resourceType, caldavxml.schedule_inbox)
         self.assertEquals(inbox.name, "inbox")
         self.assertEquals(inbox.url, "/calendars/__uids__/user01/inbox/")
-        self.assertEquals(inbox.ctag, "a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0")
+        self.assertEquals(inbox.changeToken, "a483dab3-1391-445b-b1c3-5ae9dfc81c2f#0")
 
-        self.assertEquals(outbox.resourceType, caldavxml.schedule_outbox)
-        self.assertEquals(outbox.name, None)
-        self.assertEquals(outbox.url, "/calendars/__uids__/user01/outbox/")
-        self.assertEquals(outbox.ctag, None)
-
         self.assertEqual({}, self.client.xmpp)
 
 
     def test_extractCalendarsXMPP(self):
         """
         If there is XMPP push information in a calendar home PROPFIND response,
-        L{SnowLeopard._extractCalendars} finds it and records it.
+        L{OS_X_10_6._extractCalendars} finds it and records it.
         """
         home = "/calendars/__uids__/user01/"
         self.client._extractCalendars(
-            CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP, home)
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_WITH_XMPP),
+            home
+        )
         self.assertEqual({
                 home: XMPPPush(
                     "xmpp.example.invalid:1952",
@@ -1269,13 +1256,12 @@
     def test_handleMissingXMPP(self):
         home = "/calendars/__uids__/user01/"
         self.client._extractCalendars(
-            CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING, home)
+            self.client._parseMultiStatus(CALENDAR_HOME_PROPFIND_RESPONSE_XMPP_MISSING), home)
         self.assertEqual({}, self.client.xmpp)
 
-
     def test_changeEventAttendee(self):
         """
-        SnowLeopard.changeEventAttendee removes one attendee from an
+        OS_X_10_6.changeEventAttendee removes one attendee from an
         existing event and appends another.
         """
         requests = self.interceptRequests()
@@ -1299,20 +1285,17 @@
         self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
 
         consumer = MemoryConsumer()
-        finished = body.startProducing(consumer)
-        def cbFinished(ignored):
-            vevent = Component.fromString(consumer.value())
-            attendees = tuple(vevent.mainComponent().properties("ATTENDEE"))
-            self.assertEquals(len(attendees), 2)
-            self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
-            self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
-        finished.addCallback(cbFinished)
-        return finished
+        yield body.startProducing(consumer)
+        vevent = Component.fromString(consumer.value())
+        attendees = tuple(vevent.mainComponent().properties("ATTENDEE"))
+        self.assertEquals(len(attendees), 2)
+        self.assertEquals(attendees[0].parameterValue('CN'), 'User 01')
+        self.assertEquals(attendees[1].parameterValue('CN'), 'Some Other Guy')
 
 
     def test_addEvent(self):
         """
-        L{SnowLeopard.addEvent} PUTs the event passed to it to the
+        L{OS_X_10_6.addEvent} PUTs the event passed to it to the
         server and updates local state to reflect its existence.
         """
         requests = self.interceptRequests()
@@ -1354,7 +1337,7 @@
     @inlineCallbacks
     def test_addInvite(self):
         """
-        L{SnowLeopard.addInvite} PUTs the event passed to it to the
+        L{OS_X_10_6.addInvite} PUTs the event passed to it to the
         server and updates local state to reflect its existence, but
         it also does attendee auto-complete and free-busy checks before
         the PUT.
@@ -1370,7 +1353,7 @@
         self.client.outbox = "/calendars/__uids__/user01/outbox/"
 
         @inlineCallbacks
-        def _testReport(*args):
+        def _testReport(*args, **kwargs):
             expectedResponseCode, method, url, headers, body = args
             self.assertEqual(expectedResponseCode, MULTI_STATUS)
             self.assertEqual(method, 'REPORT')
@@ -1406,11 +1389,11 @@
             
             returnValue(response)
         
-        def _testPost02(*args):
-            return _testPost(*args, attendee="ATTENDEE:mailto:user02 at example.com")
+        def _testPost02(*args, **kwargs):
+            return _testPost(*args, attendee="ATTENDEE:mailto:user02 at example.com", **kwargs)
         
-        def _testPost03(*args):
-            return _testPost(*args, attendee="ATTENDEE:mailto:user03 at example.com")
+        def _testPost03(*args, **kwargs):
+            return _testPost(*args, attendee="ATTENDEE:mailto:user03 at example.com", **kwargs)
         
         @inlineCallbacks
         def _testPut(*args, **kwargs):
@@ -1435,15 +1418,15 @@
         
         requests = [_testReport, _testPost02, _testReport, _testPost03, _testPut,]
         
-        def _requestHandler(*args):
+        def _requestHandler(*args, **kwargs):
             handler = requests.pop(0)
-            return handler(*args)
+            return handler(*args, **kwargs)
         self.client._request = _requestHandler
         yield self.client.addInvite('/mumble/frotz.ics', vcalendar)
 
     def test_deleteEvent(self):
         """
-        L{SnowLeopard.deleteEvent} DELETEs the event at the relative
+        L{OS_X_10_6.deleteEvent} DELETEs the event at the relative
         URL passed to it and updates local state to reflect its
         removal.
         """
@@ -1475,9 +1458,9 @@
         return d
 
 
-class UpdateCalendarTests(SnowLeopardMixin, TestCase):
+class UpdateCalendarTests(OS_X_10_6Mixin, TestCase):
     """
-    Tests for L{SnowLeopard._updateCalendar}.
+    Tests for L{OS_X_10_6._updateCalendar}.
     """
 
     _CALENDAR_PROPFIND_RESPONSE_BODY = """\
@@ -1612,12 +1595,12 @@
 
         calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
         self.client._calendars[calendar.url] = calendar
-        self.client._updateCalendar(calendar)
+        self.client._updateCalendar(calendar, "1234")
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('PROPFIND', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1628,7 +1611,7 @@
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         # Someone else comes along and gets rid of the event
         del self.client._events["/something/anotherthing.ics"]
@@ -1655,12 +1638,12 @@
 
         calendar = Calendar(None, set(('VEVENT',)), 'calendar', '/something/', None)
         self.client._calendars[calendar.url] = calendar
-        self.client._updateCalendar(calendar)
+        self.client._updateCalendar(calendar, "1234")
         result, req = requests.pop(0)
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('PROPFIND', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1671,7 +1654,7 @@
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1685,7 +1668,7 @@
         expectedResponseCode, method, url, _ignore_headers, _ignore_body = req
         self.assertEqual('REPORT', method)
         self.assertEqual('http://127.0.0.1/something/', url)
-        self.assertEqual(MULTI_STATUS, expectedResponseCode)
+        self.assertEqual((MULTI_STATUS,), expectedResponseCode)
 
         result.callback(
             MemoryResponse(
@@ -1696,13 +1679,13 @@
         self.assertTrue(self.client._events['/something/else.ics'].etag is not None)
 
 
-class VFreeBusyTests(SnowLeopardMixin, TestCase):
+class VFreeBusyTests(OS_X_10_6Mixin, TestCase):
     """
-    Tests for L{SnowLeopard.requestAvailability}.
+    Tests for L{OS_X_10_6.requestAvailability}.
     """
     def test_requestAvailability(self):
         """
-        L{SnowLeopard.requestAvailability} accepts a date range and a set of
+        L{OS_X_10_6.requestAvailability} accepts a date range and a set of
         account uuids and issues a VFREEBUSY request.  It returns a Deferred
         which fires with a dict mapping account uuids to availability range
         information.

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_population.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_population.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_population.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 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.
@@ -37,10 +37,37 @@
         for user in users:
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=1.23, user=user))
+                    duration=1.23, user=user, client_type="test", client_id="1234"))
         self.assertEqual(len(users), logger.countUsers())
 
 
+    def test_countClients(self):
+        """
+        L{ReportStatistics.countClients} returns the number of clients observed to
+        have acted in the simulation.
+        """
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                    type='response', method='GET', success=True,
+                    duration=1.23, user="user01", client_type="test", client_id=client))
+        self.assertEqual(len(clients), logger.countClients())
+
+
+    def test_clientFailures(self):
+        """
+        L{ReportStatistics.countClients} returns the number of clients observed to
+        have acted in the simulation.
+        """
+        logger = ReportStatistics()
+        clients = ['c01', 'c02', 'c03']
+        for client in clients:
+            logger.observe(dict(
+                    type='client-failure', reason="testing %s" % (client,)))
+        self.assertEqual(len(clients), logger.countClientFailures())
+
+
     def test_noFailures(self):
         """
         If fewer than 1% of requests fail, fewer than 1% of requests take 5
@@ -50,7 +77,7 @@
         logger = ReportStatistics()
         logger.observe(dict(
                 type='response', method='GET', success=True,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
         self.assertEqual([], logger.failures())
 
 
@@ -60,13 +87,13 @@
         list containing a string describing this.
         """
         logger = ReportStatistics()
-        for i in range(98):
+        for _ignore in range(98):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
         logger.observe(dict(
                 type='response', method='GET', success=False,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
         self.assertEqual(
             ["Greater than 1% GET failed"],
             logger.failures())
@@ -79,14 +106,14 @@
         describing that.
         """
         logger = ReportStatistics()
-        for i in range(94):
+        for _ignore in range(94):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
-        for i in range(5):
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
+        for _ignore in range(5):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=3.5, user='user02'))
+                    duration=3.5, user='user02', client_type="test", client_id="1234"))
         self.assertEqual(
             ["Greater than 5% GET exceeded 3 second response time"],
             logger.failures())
@@ -99,13 +126,13 @@
         describing that.
         """
         logger = ReportStatistics()
-        for i in range(98):
+        for _ignore in range(98):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
         logger.observe(dict(
                 type='response', method='GET', success=True,
-                duration=5.5, user='user01'))
+                duration=5.5, user='user01', client_type="test", client_id="1234"))
         self.assertEqual(
             ["Greater than 1% GET exceeded 5 second response time"],
             logger.failures())
@@ -116,19 +143,19 @@
         The counts for one method do not affect the results of another method.
         """
         logger = ReportStatistics()
-        for i in range(99):
+        for _ignore in range(99):
             logger.observe(dict(
                     type='response', method='GET', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
             logger.observe(dict(
                     type='response', method='POST', success=True,
-                    duration=2.5, user='user01'))
+                    duration=2.5, user='user01', client_type="test", client_id="1234"))
 
         logger.observe(dict(
                 type='response', method='GET', success=False,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
         logger.observe(dict(
                 type='response', method='POST', success=False,
-                duration=2.5, user='user01'))
+                duration=2.5, user='user01', client_type="test", client_id="1234"))
 
         self.assertEqual([], logger.failures())

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_profiles.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_profiles.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -154,7 +154,20 @@
 END:VCALENDAR
 """
 
+INBOX_REPLY =  """\
+BEGIN:VCALENDAR
+METHOD:REPLY
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+ORGANIZER;CN="User 01":mailto:user1 at example.com
+ATTENDEE;PARTSTAT=ACCEPTED:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+"""
 
+
 class AnyUser(object):
     def __getitem__(self, index):
         return _AnyRecord(index)
@@ -197,18 +210,28 @@
 
     @ivar rescheduled: A set of event URLs which will not allow
         attendee changes due to a changed schedule tag.
+    @ivar _pendingFailures: dict mapping URLs to failure objects
     """
     def __init__(self, number):
         self._events = {}
         self._calendars = {}
+        self._pendingFailures = {}
         self.record = _DirectoryRecord(
             "user%02d" % (number,), "user%02d" % (number,),
             "User %02d" % (number,), "user%02d at example.org" % (number,))
         self.email = "mailto:user%02d at example.com" % (number,)
         self.uuid = "urn:uuid:user%02d" % (number,)
         self.rescheduled = set()
+        self.started = True
 
 
+    def _failDeleteWithObject(self, href, failureObject):
+        """
+        Accessor for inserting intentional failures for deletes.
+        """
+        self._pendingFailures[href] = failureObject
+
+
     def addEvent(self, href, vevent):
         self._events[href] = Event(href, None, vevent)
         return succeed(None)
@@ -218,12 +241,16 @@
         return self.addEvent(href, vevent)
 
 
-    def deleteEvent(self, href):
+    def deleteEvent(self, href,):
         del self._events[href]
         calendar, uid = href.rsplit('/', 1)
         del self._calendars[calendar + '/'].events[uid]
+        if href in self._pendingFailures:
+            failureObject = self._pendingFailures.pop(href)
+            return fail(failureObject)
+        else:
+            return succeed(None)
 
-
     def updateEvent(self, href):
         self.rescheduled.remove(href)
         return succeed(None)
@@ -742,6 +769,58 @@
         clock.advance(randomDelay)
 
 
+    def test_inboxReply(self):
+        """
+        When an inbox item that contains a reply is seen by the client, it
+        deletes it immediately.
+        """
+        userNumber = 1
+        clock = Clock()
+        inboxURL = '/some/inbox/'
+        vevent = Component.fromString(INBOX_REPLY)
+        inbox = Calendar(
+            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
+        client = StubClient(userNumber)
+        client._calendars[inboxURL] = inbox
+
+        inboxEvent = Event(inboxURL + u'4321.ics', None, vevent)
+        client._setEvent(inboxEvent.url, inboxEvent)
+        accepter = Accepter(clock, self.sim, client, userNumber) 
+        accepter.eventChanged(inboxEvent.url)
+        clock.advance(3)
+        self.assertNotIn(inboxEvent.url, client._events)
+        self.assertNotIn('4321.ics', inbox.events)
+
+
+    def test_inboxReplyFailedDelete(self):
+        """
+        When an inbox item that contains a reply is seen by the client, it
+        deletes it immediately.  If the delete fails, the appropriate response
+        code is returned.
+        """
+        userNumber = 1
+        clock = Clock()
+        inboxURL = '/some/inbox/'
+        vevent = Component.fromString(INBOX_REPLY)
+        inbox = Calendar(
+            caldavxml.schedule_inbox, set(), u'the inbox', inboxURL, None)
+        client = StubClient(userNumber)
+        client._calendars[inboxURL] = inbox
+
+        inboxEvent = Event(inboxURL + u'4321.ics', None, vevent)
+        client._setEvent(inboxEvent.url, inboxEvent)
+        client._failDeleteWithObject(inboxEvent.url, IncorrectResponseCode(
+                    NO_CONTENT,
+                    Response(
+                        ('HTTP', 1, 1), PRECONDITION_FAILED,
+                        'Precondition Failed', None, None)))
+        accepter = Accepter(clock, self.sim, client, userNumber) 
+        accepter.eventChanged(inboxEvent.url)
+        clock.advance(3)
+        self.assertNotIn(inboxEvent.url, client._events)
+        self.assertNotIn('4321.ics', inbox.events)
+
+
     def test_acceptInvitation(self):
         """
         If the client is an attendee on an event and the PARTSTAT is

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_sim.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_sim.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2011 Apple Inc. All rights reserved.
+# Copyright (c) 2011-2012 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.
@@ -21,13 +21,13 @@
 from twisted.python.log import msg
 from twisted.python.usage import UsageError
 from twisted.python.filepath import FilePath
-from twisted.internet.defer import Deferred
+from twisted.internet.defer import Deferred, succeed
 from twisted.trial.unittest import TestCase
 
 from twistedcaldav.directory.directory import DirectoryRecord
 
 from contrib.performance.stats import NormalDistribution
-from contrib.performance.loadtest.ical import SnowLeopard
+from contrib.performance.loadtest.ical import OS_X_10_6
 from contrib.performance.loadtest.profiles import Eventer, Inviter, Accepter
 from contrib.performance.loadtest.population import (
     SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
@@ -37,6 +37,10 @@
 
 VALID_CONFIG = {
     'server': 'tcp:127.0.0.1:8008',
+    'webadmin': {
+        'enabled': True,
+        'HTTPPort': 8080,
+        },
     'arrival': {
         'factory': 'contrib.performance.loadtest.population.SmoothRampUp',
         'params': {
@@ -147,6 +151,9 @@
 
             def run(self):
                 return self._runResult
+
+            def stop(self):
+                return succeed(None)
                 
         class BrokenProfile(object):
             def __init__(self, reactor, simulator, client, userNumber, runResult):
@@ -165,7 +172,7 @@
                 [ProfileType(BrokenProfile, {'runResult': profileRunResult})]))
         sim = CalendarClientSimulator(
             [self._user('alice')], Populator(None), params, None, 'http://example.com:1234/')
-        sim.add(1)
+        sim.add(1, 1)
         sim.stop()
         clientRunResult.errback(RuntimeError("Some fictional client problem"))
         profileRunResult.errback(RuntimeError("Some fictional profile problem"))
@@ -186,7 +193,7 @@
         for thunk in self._whenRunning:
             thunk()
         msg(self.message)
-        for phase, event, thunk in self._triggers:
+        for _ignore_phase, event, thunk in self._triggers:
             if event == 'shutdown':
                 thunk()
 
@@ -209,7 +216,7 @@
         self.events.append(event)
 
 
-    def report(self):
+    def report(self, output):
         self.reported = True
 
 
@@ -241,7 +248,7 @@
         exc = self.assertRaises(
             SystemExit, StubSimulator.main, ['--config', config.path])
         self.assertEquals(
-            exc.args, (StubSimulator(None, None, None).run(),))
+            exc.args, (StubSimulator(None, None, None, None).run(),))
 
 
     def test_createSimulator(self):
@@ -252,7 +259,7 @@
         """
         server = 'http://127.0.0.7:1243/'
         reactor = object()
-        sim = LoadSimulator(server, None, None, reactor=reactor)
+        sim = LoadSimulator(server, None, None, None, reactor=reactor)
         calsim = sim.createSimulator()
         self.assertIsInstance(calsim, CalendarClientSimulator)
         self.assertIsInstance(calsim.reactor, LagTrackingReactor)
@@ -432,7 +439,7 @@
 
         reactor = object()
         sim = LoadSimulator(
-            None, Arrival(FakeArrival, {'x': 3, 'y': 2}), None, reactor=reactor)
+            None, None, Arrival(FakeArrival, {'x': 3, 'y': 2}), None, reactor=reactor)
         arrival = sim.createArrivalPolicy()
         self.assertIsInstance(arrival, FakeArrival)
         self.assertIdentical(arrival.reactor, sim.reactor)
@@ -448,7 +455,7 @@
         config = FilePath(self.mktemp())
         config.setContent(writePlistToString({
                     "clients": [{
-                            "software": "contrib.performance.loadtest.ical.SnowLeopard",
+                            "software": "contrib.performance.loadtest.ical.OS_X_10_6",
                             "params": {"foo": "bar"},
                             "profiles": [{
                                     "params": {
@@ -466,7 +473,7 @@
         sim = LoadSimulator.fromCommandLine(['--config', config.path])
         expectedParameters = PopulationParameters()
         expectedParameters.addClient(
-            3, ClientType(SnowLeopard, {"foo": "bar"}, [ProfileType(Eventer, {
+            3, ClientType(OS_X_10_6, {"foo": "bar"}, [ProfileType(Eventer, {
                             "interval": 25,
                             "eventStartDistribution": NormalDistribution(123, 456)})]))
         self.assertEquals(sim.parameters, expectedParameters)
@@ -483,7 +490,7 @@
         sim = LoadSimulator.fromCommandLine(['--config', config.path])
         expectedParameters = PopulationParameters()
         expectedParameters.addClient(
-            1, ClientType(SnowLeopard, {}, [Eventer, Inviter, Accepter]))
+            1, ClientType(OS_X_10_6, {}, [Eventer, Inviter, Accepter]))
         self.assertEquals(sim.parameters, expectedParameters)
 
 
@@ -509,11 +516,12 @@
         observers = [Observer()]
         sim = LoadSimulator(
             "http://example.com:123/",
+            None,
             Arrival(lambda reactor: NullArrival(), {}),
             None, observers, reactor=Reactor())
         io = StringIO()
         sim.run(io)
-        self.assertEquals(io.getvalue(), "PASS\n")
+        self.assertEquals(io.getvalue(), "\n*** PASS\n")
         self.assertTrue(observers[0].reported)
         self.assertEquals(
             observers[0].events[0]['message'], (Reactor.message,))

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_trafficlogger.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_trafficlogger.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_trafficlogger.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -30,7 +30,7 @@
     An interface which can be used to verify some interface-related behavior of
     L{loggedReactor}.
     """
-    def probe():
+    def probe(): #@NoSelf
         pass
 
 

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_webadmin.py (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/test_webadmin.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_webadmin.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/test_webadmin.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,140 @@
+##
+# Copyright (c) 2012 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 twisted.trial.unittest import TestCase
+from contrib.performance.loadtest.webadmin import LoadSimAdminResource
+
+class WebAdminTests(TestCase):
+    """
+    Tests for L{LoadSimAdminResource}.
+    """
+
+    class FakeReporter(object):
+        
+        def generateReport(self, output):
+            output.write("FakeReporter")
+
+
+    class FakeReactor(object):
+        
+        def __init__(self):
+            self.running = True
+        
+        def stop(self):
+            self.running = False
+
+
+    class FakeLoadSim(object):
+        
+        def __init__(self):
+            self.reactor = WebAdminTests.FakeReactor()
+            self.reporter = WebAdminTests.FakeReporter()
+            self.running = True
+        
+        def stop(self):
+            self.running = False
+
+    
+    class FakeRequest(object):
+        
+        def __init__(self, **kwargs):
+            self.args = kwargs
+
+
+    def test_resourceGET(self):
+        """
+        Test render_GET
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        
+        response = resource.render_GET(WebAdminTests.FakeRequest())
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        
+    def test_resourcePOST_Stop(self):
+        """
+        Test render_POST when Stop button is clicked
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(resource.token,),
+            stop=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) == -1)
+        self.assertTrue(response.find("FakeReporter") != -1)
+        self.assertFalse(loadsim.running)
+        
+    def test_resourcePOST_Stop_BadToken(self):
+        """
+        Test render_POST when Stop button is clicked but token is wrong
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=("xyz",),
+            stop=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find("FakeReporter") == -1)
+        self.assertTrue(loadsim.running)
+        
+    def test_resourcePOST_Results(self):
+        """
+        Test render_POST when Results button is clicked
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=(resource.token,),
+            results=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find("FakeReporter") != -1)
+        self.assertTrue(loadsim.running)
+        
+    def test_resourcePOST_Results_BadToken(self):
+        """
+        Test render_POST when Results button is clicked and token is wrong
+        """
+        
+        loadsim = WebAdminTests.FakeLoadSim()
+        resource = LoadSimAdminResource(loadsim)
+        self.assertTrue(loadsim.reactor.running)
+       
+        response = resource.render_POST(WebAdminTests.FakeRequest(
+            token=("xyz",),
+            results=None,
+        ))
+        self.assertTrue(response.startswith("<html>"))
+        self.assertTrue(response.find(resource.token) != -1)
+        self.assertTrue(response.find("FakeReporter") == -1)
+        self.assertTrue(loadsim.running)

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/webadmin.py (from rev 9105, CalendarServer/trunk/contrib/performance/loadtest/webadmin.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/webadmin.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/performance/loadtest/webadmin.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,110 @@
+##
+# Copyright (c) 2012 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.
+#
+##
+
+"""
+loadsim Web Admin UI.
+"""
+
+__all__ = [
+    "LoadSimAdminResource",
+]
+
+import cStringIO as StringIO
+import uuid
+
+from time import clock
+from twisted.web import resource
+
+
+class LoadSimAdminResource (resource.Resource):
+    """
+    Web administration HTTP resource.
+    """
+    isLeaf = True
+
+    HEAD = """\
+<html>
+<head>
+<style type="text/css">
+body {color:#000000;}
+h1 h2 h3 {color:#333333;}
+td {text-align: center; padding: 5px;}
+pre.light {color:#CCCCCC; font-size:12px;}
+table.rounded-corners {
+    border: 1px solid #000000; background-color:#cccccc;
+    -moz-border-radius: 5px;
+    -webkit-border-radius: 5px;
+    -khtml-border-radius: 5px;
+    border-radius: 5px;
+}
+</style>
+</head>
+<body>
+    <h1>Load Simulator Web Admin</h1>
+    <form method="POST">
+        <input name="token" type="hidden" value="%s" />
+        <table class="rounded-corners">
+        <tr><td><input name="results" type="submit" value="Refresh" /></td></tr>
+        <tr><td><input name="stop" type="submit" value="Stop Sim" /></td></tr>
+        </table>
+    </form>
+"""
+
+    BODY = """\
+</body>
+</html>
+"""
+
+    BODY_RESULTS = """\
+<pre>%s</pre><pre class="light">Generated in %.1f milliseconds</pre>
+</body>
+</html>
+"""
+
+    BODY_RESULTS_STOPPED = "<h3>LoadSim Stopped - Final Results</h3>" + BODY_RESULTS
+
+    def __init__(self, loadsim):
+        self.loadsim = loadsim
+        self.token = str(uuid.uuid4())
+
+    def render_GET(self, request):
+        return self._renderReport()
+
+    def render_POST(self, request):
+        html = self.HEAD + self.BODY
+        if 'token' not in request.args or request.args['token'][0] != self.token:
+            return html % (self.token,)
+
+        if 'stop' in request.args:
+            self.loadsim.stop()
+            return self._renderReport(True)
+        elif 'results' in request.args:
+            return self._renderReport()
+        return html % (self.token,)
+
+    def _renderReport(self, stopped=False):
+        report = StringIO.StringIO()
+        before = clock()
+        self.loadsim.reporter.generateReport(report)
+        after = clock()
+        ms = (after - before) * 1000
+        if stopped:
+            html = self.HEAD + self.BODY_RESULTS_STOPPED
+            return html % (None, report.getvalue(), ms)
+        else:
+            html = self.HEAD + self.BODY_RESULTS
+            return html % (self.token, report.getvalue(), ms)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/tools/request_monitor.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/tools/request_monitor.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/contrib/tools/request_monitor.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -161,7 +161,7 @@
             stdout=PIPE, stderr=STDOUT,
         )
         output, _ignore_ = child.communicate()
-        return output.splitlines[-2].split()[2]
+        return output.splitlines()[-1].split()[2]
     elif OS == "Linux":
         child = Popen(
             args=[
@@ -455,6 +455,11 @@
 
 
         times.sort()
+        if len(times) == 0:
+            print "No data to analyze"
+            time.sleep(10)
+            continue
+            
         startTime = times[0]
         endTime = times[-1]
         deltaTime = endTime - startTime
@@ -579,9 +584,9 @@
         if lineRange is not None:
             break
 
-        time.sleep(10)
-
     except Exception, e:
         print "Script failure", e
         if debug:
             print traceback.print_exc()
+
+    time.sleep(10)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/doc/Admin/LoadSimulation.txt
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/doc/Admin/LoadSimulation.txt	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/doc/Admin/LoadSimulation.txt	2012-04-13 20:13:22 UTC (rev 9107)
@@ -289,9 +289,9 @@
 Scalability
 ---------------------
 
-A good amount of activity can be generated by a single client sim instance, and that should be suitable for most cases. However, if your task is performance or scalability testing, you will likely want to use more than a single CPU core to generate the workload. By adding a 'workers' key to the sim's config file you can specify the use of additional instances on the local host, and / or remote hosts.
+A good amount of activity can be generated by a single client sim instance, and that should be suitable for most cases. However, if your task is performance or scalability testing, you will likely want to generate more load than can be presented by a single CPU core (which is all you can get from a single Python process). By adding a 'workers' array to the sim's config file you can specify the use of additional sim instances on the local host, and / or remote hosts. In this configuration, the master process will distribute work across all the workers. In general, you shouldn't need additional workers unless you are approaching CPU saturation for your existing sim instance(s). The "lag" statistic is another useful metric for determining whether the client sim is hitting its targets - if it gets too high, consider adding workers.
 
-The specific approach you take when configuring a high load depends on your goals and available resources. If your goal is to beat down a server until it melts into the floor, it is legitimate to use a less 'accurate' simulation by reducing the timers and intervals in the client sim's behavior configuration. If instead you wish to see how many 'accurate' clients your server can service, you will want to stick with reasonable values for timers and intervals, and instead increase load by using larger numbers of clients (in the 'arrival' config), and / or running additional instances. This tradeoff might be summarized: accuracy vs file descriptors.
+The specific approach you take when configuring a high load depends on your goals and available resources. If your goal is to beat down a server until it melts into the floor, it is legitimate to use a less accurate simulation by reducing the timers and intervals in the client sim's behavior configuration. If instead you wish to see how many 'realistic' clients your server can service, you will want to stick with reasonable values for timers and intervals, and instead increase load by configuring more user accounts (in the 'arrival' section of the config file, and the separate user accounts file).
 
 To use four instances on the local host::
 
@@ -319,7 +319,7 @@
 
 **When using remote hosts, the ssh commands must work in an unattended fashion, so configure SSH keys as needed**. Also, each remote host needs to have a Calendar Server SVN checkout. In this example, the hosts blade2 and blade3 need to have an SVN checkout of Calendar Server at ~/ccs/CalendarServer.
 
-Configuration of the additional workers is handled by the master, so you need not distribute the sim's config file to the other hosts. Each instance gets an identical copy of the config, so the overall load presented to the server scales linearly with the number of workers. Two workers present twice as much load as one worker, assuming they both are reporting sufficiently low 'lag' values.
+Configuration of the additional workers is handled by the master, so you need not distribute the sim's config file to the other hosts. Each instance gets an identical copy of the config. The amount of work attempted by the sim is not changed by adding workers; instead, the master distributes work (i.e. user accounts) across the workers. To do more work, add user accounts.
 
 When running the sim using multiple instances, the standard output of each child instance is sent to the master. For example, when starting with four instances::
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/support/build.sh
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/support/build.sh	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/support/build.sh	2012-04-13 20:13:22 UTC (rev 9107)
@@ -547,7 +547,6 @@
 
   www_get ${f_hash} "${name}" "${srcdir}" "${uri}";
 
-
   export              PATH="${dstroot}/bin:${PATH}";
   export    C_INCLUDE_PATH="${dstroot}/include:${C_INCLUDE_PATH:-}";
   export   LD_LIBRARY_PATH="${dstroot}/lib:${LD_LIBRARY_PATH:-}";
@@ -555,13 +554,17 @@
   export           LDFLAGS="-L${dstroot}/lib ${LDFLAGS:-} ";
   export DYLD_LIBRARY_PATH="${dstroot}/lib:${DYLD_LIBRARY_PATH:-}";
 
-  if "${do_setup}" && (
-      "${force_setup}" || "${do_bundle}" || [ ! -d "${dstroot}" ]); then
-    echo "Building ${name}...";
-    cd "${srcdir}";
-    ./configure --prefix="${dstroot}" "$@";
-    jmake;
-    jmake install;
+  if "${do_setup}"; then
+    if "${force_setup}" || "${do_bundle}" || [ ! -d "${dstroot}" ]; then
+      echo "Building ${name}...";
+      cd "${srcdir}";
+      ./configure --prefix="${dstroot}" "$@";
+      jmake;
+      jmake install;
+    else
+      echo "Using built ${name}.";
+      echo "";
+    fi;
   fi;
 }
 
@@ -614,7 +617,10 @@
     init_py;
   fi;
 
-  if ! type -P memcached > /dev/null; then
+  if type -P memcached > /dev/null; then
+    echo "Using system memcached.";
+    echo "";
+  else
     local le="libevent-2.0.17-stable";
     local mc="memcached-1.4.13";
     c_dependency -m "dad64aaaaff16b5fbec25160c06fee9a" \
@@ -625,7 +631,10 @@
       "http://memcached.googlecode.com/files/${mc}.tar.gz";
   fi;
 
-  if ! type -P postgres > /dev/null; then
+  if type -P postgres > /dev/null; then
+    echo "Using system Postgres.";
+    echo "";
+  else
     local pgv="9.1.2";
     local pg="postgresql-${pgv}";
 
@@ -642,7 +651,10 @@
     :;
   fi;
 
-  if ! find_header ldap.h; then
+  if find_header ldap.h; then
+    echo "Using system OpenLDAP.";
+    echo "";
+  else
     c_dependency -m "ec63f9c2add59f323a0459128846905b" \
       "OpenLDAP" "openldap-2.4.25" \
       "http://www.openldap.org/software/download/OpenLDAP/openldap-release/openldap-2.4.25.tgz" \
@@ -672,8 +684,8 @@
     "Zope Interface" "zope.interface" "${zi}" \
     "http://www.zope.org/Products/ZopeInterface/${zv}/${zi}.tar.gz";
 
-  local po="pyOpenSSL-0.13";
-  py_dependency -v 0.13 -m "767bca18a71178ca353dff9e10941929" \
+  local po="pyOpenSSL-0.10";
+  py_dependency -v 0.9 -m "34db8056ec53ce80c7f5fc58bee9f093" \
     "PyOpenSSL" "OpenSSL" "${po}" \
     "http://pypi.python.org/packages/source/p/pyOpenSSL/${po}.tar.gz";
 
@@ -721,7 +733,7 @@
     "${pypi}/p/python-ldap/${ld}.tar.gz";
 
   # XXX actually PyCalendar should be imported in-place.
-  py_dependency -fe -i "src" -r 189 \
+  py_dependency -fe -i "src" -r 190 \
     "pycalendar" "pycalendar" "pycalendar" \
     "http://svn.mulberrymail.com/repos/PyCalendar/branches/server";
 
@@ -752,6 +764,18 @@
     "pytz" "pytz" "${tz}" \
     "http://pypi.python.org/packages/source/p/pytz/${tz}.tar.gz";
 
+  local pv="2.5";
+  local pc="pycrypto-${pv}";
+  py_dependency -o -v "${pv}" -m "783e45d4a1a309e03ab378b00f97b291" \
+    "PyCrypto" "pycrypto" "${pc}" \
+    "http://ftp.dlitz.net/pub/dlitz/crypto/pycrypto/${pc}.tar.gz";
+
+  local v="0.1.2";
+  local p="pyasn1-${v}";
+  py_dependency -o -v "${v}" -m "a7c67f5880a16a347a4d3ce445862a47" \
+    "pyasn1" "pyasn1" "${p}" \
+    "http://pypi.python.org/packages/source/p/pyasn1/pyasn1-0.1.2.tar.gz";
+
   svn_get "CalDAVTester" "${top}/CalDAVTester" \
       "${svn_uri_base}/CalDAVTester/trunk" HEAD;
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/support/submit
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/support/submit	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/support/submit	2012-04-13 20:13:22 UTC (rev 9107)
@@ -174,7 +174,7 @@
     merge_flags="";
   fi;
 
-  sudo buildit "${wc}" CALENDARSERVER_CACHE_DEPS="${CALENDARSERVER_CACHE_DEPS-${wd}/.dependencies}" \
+  sudo buildit "${wc}" \
     $(file /System/Library/Frameworks/Python.framework/Versions/Current/Python | sed -n -e 's|^.*(for architecture \([^)][^)]*\).*$|-arch \1|p' | sed 's|ppc7400|ppc|') \
     ${merge_flags};
 
@@ -190,6 +190,7 @@
 else
   echo "";
   echo "Submitting sources for ${project_version}...";
+  rm -rf "${wc}/.dependencies";
   submitproject "${wc}" ${releases};
 fi;
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/test
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/test	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/test	2012-04-13 20:13:22 UTC (rev 9107)
@@ -72,7 +72,7 @@
 
 if [ $# -gt 0 ]; then
   test_modules="$@";
-  flaky=false;
+  flaky=true;
 else
   test_modules="calendarserver twistedcaldav twext txdav contrib ${m_twisted}";
   flaky=true;

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/internet/sendfdport.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/internet/sendfdport.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/internet/sendfdport.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -22,8 +22,8 @@
 
 from os import close
 from errno import EAGAIN, ENOBUFS
-from socket import (socketpair, fromfd, error as SocketError,
-                    AF_INET, AF_UNIX, SOCK_STREAM, SOCK_DGRAM)
+from socket import (socketpair, fromfd, error as SocketError, AF_UNIX,
+                    SOCK_STREAM, SOCK_DGRAM)
 
 from twisted.python import log
 
@@ -32,6 +32,7 @@
 
 from twext.python.sendmsg import sendmsg, recvmsg
 from twext.python.sendfd import sendfd, recvfd
+from twext.python.sendmsg import getsockfam
 
 class InheritingProtocol(Protocol, object):
     """
@@ -273,7 +274,7 @@
                 raise
         else:
             try:
-                skt = fromfd(fd, AF_INET, SOCK_STREAM)
+                skt = fromfd(fd, getsockfam(fd), SOCK_STREAM)
                 # XXX it could be AF_UNIX, I guess?  or even something else?
                 # should this be on the transportFactory's side of things?
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/python/sendmsg.c
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/python/sendmsg.c	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/python/sendmsg.c	2012-04-13 20:13:22 UTC (rev 9107)
@@ -42,12 +42,15 @@
 
 static PyObject *sendmsg_sendmsg(PyObject *self, PyObject *args, PyObject *keywds);
 static PyObject *sendmsg_recvmsg(PyObject *self, PyObject *args, PyObject *keywds);
+static PyObject *sendmsg_getsockfam(PyObject *self, PyObject *args, PyObject *keywds);
 
 static PyMethodDef sendmsg_methods[] = {
     {"sendmsg", (PyCFunction) sendmsg_sendmsg, METH_VARARGS | METH_KEYWORDS,
      NULL},
     {"recvmsg", (PyCFunction) sendmsg_recvmsg, METH_VARARGS | METH_KEYWORDS,
      NULL},
+    {"getsockfam", (PyCFunction) sendmsg_getsockfam,
+     METH_VARARGS | METH_KEYWORDS, NULL},
     {NULL, NULL, 0, NULL}
 };
 
@@ -404,3 +407,18 @@
     return final_result;
 }
 
+static PyObject *sendmsg_getsockfam(PyObject *self, PyObject *args,
+                                    PyObject *keywds) {
+    int fd;
+    struct sockaddr sa;
+    static char *kwlist[] = {"fd", NULL};
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "i", kwlist, &fd)) {
+        return NULL;
+    }
+    socklen_t sz = sizeof(sa);
+    if (getsockname(fd, &sa, &sz)) {
+        PyErr_SetFromErrno(sendmsg_socket_error);
+        return NULL;
+    }
+    return Py_BuildValue("i", sa.sa_family);
+}

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/http.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/http.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/http.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
+# Copyright (c) 2005-2012 Apple Computer, Inc. All rights reserved.
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -18,8 +18,6 @@
 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 # SOFTWARE.
-#
-# DRI: Wilfredo Sanchez, wsanchez at apple.com
 ##
 
 """
@@ -301,7 +299,7 @@
     """
     def msg(err):
         if what is not None:
-            log.msg("%s while %s" % (err, what))
+            log.debug("%s while %s" % (err, what))
 
     if failure.check(IOError, OSError):
         e = failure.value[0]

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/method/put_common.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/method/put_common.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -33,7 +33,7 @@
 from twext.web2.dav.fileop import copy, delete, put
 from twext.web2.dav.http import ErrorResponse
 from twext.web2.dav.resource import TwistedGETContentMD5
-from twext.web2.dav.stream import MD5StreamWrapper
+from twext.web2.stream import MD5Stream
 from twext.web2.http import HTTPError
 from twext.web2.http_headers import generateContentType
 from twext.web2.iweb import IResponse
@@ -198,7 +198,7 @@
             datastream = request.stream
             if data is not None:
                 datastream = MemoryStream(data)
-            md5 = MD5StreamWrapper(datastream)
+            md5 = MD5Stream(datastream)
             response = maybeDeferred(put, md5, destination.fp)
 
         response = waitForDeferred(response)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/resource.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/resource.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/resource.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -948,51 +948,62 @@
             if the authentication scheme is unsupported, or the
             credentials provided by the request are not valid.
         """
-        if not (hasattr(request, 'portal') and
-                hasattr(request, 'credentialFactories') and
-                hasattr(request, 'loginInterfaces')):
+        # Bypass normal authentication if its already been done (by SACL check)
+        if (
+            hasattr(request, "authnUser") and
+            hasattr(request, "authzUser") and
+            request.authnUser is not None and
+            request.authzUser is not None
+        ):
+            return succeed((request.authnUser, request.authzUser))
+
+        if not (
+            hasattr(request, "portal") and
+            hasattr(request, "credentialFactories") and
+            hasattr(request, "loginInterfaces")
+        ):
             request.authnUser = element.Principal(element.Unauthenticated())
             request.authzUser = element.Principal(element.Unauthenticated())
             return succeed((request.authnUser, request.authzUser))
 
-        authHeader = request.headers.getHeader('authorization')
+        authHeader = request.headers.getHeader("authorization")
 
         if authHeader is not None:
             if authHeader[0] not in request.credentialFactories:
-                log.err("Client authentication scheme %s is not "
-                        "provided by server %s"
-                        % (authHeader[0], request.credentialFactories.keys()))
+                log.err(
+                    "Client authentication scheme %s is not provided by server %s"
+                    % (authHeader[0], request.credentialFactories.keys())
+                )
                 d = UnauthorizedResponse.makeResponse(
                     request.credentialFactories,
                     request.remoteAddr
                 )
-                def _fail(response):
-                    return Failure(HTTPError(response))
-                return d.addCallback(_fail)
+                return d.addCallback(lambda response: Failure(HTTPError(response)))
             else:
                 factory = request.credentialFactories[authHeader[0]]
 
                 def gotCreds(creds):
-                    return self.principalsForAuthID(
-                        request, creds.username
-                        ).addCallback(gotDetails, creds)
+                    d = self.principalsForAuthID(request, creds.username)
+                    d.addCallback(gotDetails, creds)
+                    return d
 
                 # Try to match principals in each principal collection
                 # on the resource
                 def gotDetails(details, creds):
                     if details == (None, None):
-                        log.msg("Could not find the principal resource for user id: %s" % (creds.username,))
-                        raise HTTPError(responsecode.UNAUTHORIZED)
+                        log.msg(
+                            "Could not find the principal resource for user id: %s"
+                            % (creds.username,)
+                        )
+                        return Failure(HTTPError(responsecode.UNAUTHORIZED))
 
                     authnPrincipal = IDAVPrincipalResource(details[0])
                     authzPrincipal = IDAVPrincipalResource(details[1])
-                    return PrincipalCredentials(
-                        authnPrincipal, authzPrincipal, creds
-                    )
+                    return PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
 
                 def login(pcreds):
-                    return request.portal.login(
-                        pcreds, None, *request.loginInterfaces)
+                    return request.portal.login(pcreds, None, *request.loginInterfaces)
+
                 def gotAuth(result):
                     request.authnUser = result[1]
                     request.authzUser = result[2]
@@ -1004,9 +1015,7 @@
                     d = UnauthorizedResponse.makeResponse(
                         request.credentialFactories, request.remoteAddr
                     )
-                    d.addCallback(
-                        lambda response: Failure(HTTPError(response))
-                    )
+                    d.addCallback(lambda response: Failure(HTTPError(response)))
                     return d
 
                 d = factory.decode(authHeader[1], request)

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/stream.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/stream.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/stream.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,106 +0,0 @@
-# Copyright (c) 2009 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-#
-##
-
-"""
-Class that implements a stream that calculates the MD5 hash of the data
-as the data is read.
-"""
-
-__all__ = ["MD5StreamWrapper"]
-
-from twisted.python.hashlib import md5
-from twisted.internet.defer import Deferred
-from twext.web2.stream import SimpleStream
-
-
-class MD5StreamWrapper(SimpleStream):
-    """
-    An L{IByteStream} wrapper which computes the MD5 hash of the data read from
-    the wrapped stream.
-
-    @ivar _stream: The stream which is wrapped.
-    @ivar _md5: The object used to compute the running md5 hash.
-    @ivar _md5value: The hex encoded md5 hash, only set after C{close}.
-    """
-
-    def __init__(self, wrap):
-        if wrap is None:
-            raise ValueError("Stream to wrap must be provided")
-        self._stream = wrap
-        self._md5 = md5()
-
-
-    def _update(self, value):
-        """
-        Update the MD5 hash object.
-
-        @param value: L{None} or a L{str} with which to update the MD5 hash
-            object.
-
-        @return: C{value}
-        """
-        if value is not None:
-            self._md5.update(value)
-        return value
-
-
-    def read(self):
-        """
-        Read from the wrapped stream and update the MD5 hash object.
-        """
-        if self._stream is None:
-            raise RuntimeError("Cannot read after stream is closed")
-        b = self._stream.read()
-
-        if isinstance(b, Deferred):
-            b.addCallback(self._update)
-        else:
-            if b is not None:
-                self._md5.update(b)
-        return b
-
-
-    def close(self):
-        """
-        Compute the final hex digest of the contents of the wrapped stream.
-        """
-        SimpleStream.close(self)
-        self._md5value = self._md5.hexdigest()
-        self._stream = None
-        self._md5 = None
-
-
-    def getMD5(self):
-        """
-        Return the hex encoded MD5 digest of the contents of the wrapped
-        stream.  This may only be called after C{close}.
-
-        @rtype: C{str}
-        @raise RuntimeError: If C{close} has not yet been called.
-        """
-        if self._md5 is not None:
-            raise RuntimeError("Cannot get MD5 value until stream is closed")
-        return self._md5value

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/test/test_stream.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/test/test_stream.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/dav/test/test_stream.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,129 +0,0 @@
-# Copyright (c) 2009 Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-##
-# Copyright (c) 2005 Apple Computer, Inc. All rights reserved.
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in all
-# copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-# SOFTWARE.
-##
-
-from twisted.python.hashlib import md5
-from twisted.internet.defer import Deferred
-from twisted.trial.unittest import TestCase
-from twext.web2.stream import MemoryStream
-from twext.web2.dav.stream import MD5StreamWrapper
-
-
-class AsynchronousDummyStream(object):
-    """
-    An L{IByteStream} implementation which always returns a L{Deferred} from
-    C{read} and lets an external driver fire them.
-    """
-    def __init__(self):
-        self._readResults = []
-
-
-    def read(self):
-        result = Deferred()
-        self._readResults.append(result)
-        return result
-
-
-    def _write(self, bytes):
-        self._readResults.pop(0).callback(bytes)
-
-
-
-class MD5StreamWrapperTests(TestCase):
-    """
-    Tests for L{MD5StreamWrapper}.
-    """
-    data = "I am sorry Dave, I can't do that.\n--HAL 9000"
-    digest = md5(data).hexdigest()
-
-    def test_synchronous(self):
-        """
-        L{MD5StreamWrapper} computes the MD5 hash of the contents of the stream
-        around which it is wrapped.  It supports L{IByteStream} providers which
-        return C{str} from their C{read} method.
-        """
-        dataStream = MemoryStream(self.data)
-        md5Stream = MD5StreamWrapper(dataStream)
-
-        self.assertEquals(str(md5Stream.read()), self.data)
-        self.assertIdentical(md5Stream.read(), None)
-        md5Stream.close()
-
-        self.assertEquals(self.digest, md5Stream.getMD5())
-
-
-    def test_asynchronous(self):
-        """
-        L{MD5StreamWrapper} also supports L{IByteStream} providers which return
-        L{Deferreds} from their C{read} method.
-        """
-        dataStream = AsynchronousDummyStream()
-        md5Stream = MD5StreamWrapper(dataStream)
-
-        result = md5Stream.read()
-        dataStream._write(self.data)
-        result.addCallback(self.assertEquals, self.data)
-
-        def cbRead(ignored):
-            result = md5Stream.read()
-            dataStream._write(None)
-            result.addCallback(self.assertIdentical, None)
-            return result
-        result.addCallback(cbRead)
-
-        def cbClosed(ignored):
-            md5Stream.close()
-            self.assertEquals(md5Stream.getMD5(), self.digest)
-        result.addCallback(cbClosed)
-
-        return result
-
-
-    def test_getMD5FailsBeforeClose(self):
-        """
-        L{MD5StreamWrapper.getMD5} raises L{RuntimeError} if called before
-        L{MD5StreamWrapper.close}.
-        """
-        dataStream = MemoryStream(self.data)
-        md5Stream = MD5StreamWrapper(dataStream)
-        self.assertRaises(RuntimeError, md5Stream.getMD5)
-
-
-    def test_initializationFailsWithoutStream(self):
-        """
-        L{MD5StreamWrapper.__init__} raises L{ValueError} if passed C{None} as
-        the stream to wrap.
-        """
-        self.assertRaises(ValueError, MD5StreamWrapper, None)
-
-
-    def test_readAfterClose(self):
-        """
-        L{MD5StreamWrapper.read} raises L{RuntimeError} if called after
-        L{MD5StreamWrapper.close}.
-        """
-        dataStream = MemoryStream(self.data)
-        md5Stream = MD5StreamWrapper(dataStream)
-        md5Stream.close()
-        self.assertRaises(RuntimeError, md5Stream.read)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/stream.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/stream.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/stream.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -61,6 +61,7 @@
 from twisted.internet import interfaces as ti_interfaces, defer, reactor, protocol, error as ti_error
 from twisted.python import components, log
 from twisted.python.failure import Failure
+from twisted.python.hashlib import md5
 
 # Python 2.4.2 (only) has a broken mmap that leaks a fd every time you call it.
 if sys.version_info[0:3] != (2,4,2):
@@ -1090,17 +1091,77 @@
         return pre, post
 
         
-def substream(stream, start, end):
-    if start > end:
-        raise ValueError("start position must be less than end position %r"
-                         % ((start, end),))
-    stream = stream.split(start)[1]
-    return stream.split(end - start)[0]
+#########################
+####    MD5Stream    ####
+#########################
 
+class MD5Stream(SimpleStream):
+    """
+    An wrapper which computes the MD5 hash of the data read from the
+    wrapped stream.
+    """
 
+    def __init__(self, wrap):
+        if wrap is None:
+            raise ValueError("Stream to wrap must be provided")
+        self._stream = wrap
+        self._md5 = md5()
 
+
+    def _update(self, value):
+        """
+        Update the MD5 hash object.
+
+        @param value: L{None} or a L{str} with which to update the MD5 hash
+            object.
+
+        @return: C{value}
+        """
+        if value is not None:
+            self._md5.update(value)
+        return value
+
+
+    def read(self):
+        """
+        Read from the wrapped stream and update the MD5 hash object.
+        """
+        if self._stream is None:
+            raise RuntimeError("Cannot read after stream is closed")
+        b = self._stream.read()
+
+        if isinstance(b, Deferred):
+            b.addCallback(self._update)
+        else:
+            self._update(b)
+        return b
+
+
+    def close(self):
+        """
+        Compute the final hex digest of the contents of the wrapped stream.
+        """
+        SimpleStream.close(self)
+        self._md5value = self._md5.hexdigest()
+        self._stream = None
+        self._md5 = None
+
+
+    def getMD5(self):
+        """
+        Return the hex encoded MD5 digest of the contents of the wrapped
+        stream.  This may only be called after C{close}.
+
+        @rtype: C{str}
+        @raise RuntimeError: If C{close} has not yet been called.
+        """
+        if self._md5 is not None:
+            raise RuntimeError("Cannot get MD5 value until stream is closed")
+        return self._md5value
+
+
+
 __all__ = ['IStream', 'IByteStream', 'FileStream', 'MemoryStream', 'CompoundStream',
            'readAndDiscard', 'fallbackSplit', 'ProducerStream', 'StreamProducer',
-           'BufferedStream', 'readStream', 'ProcessStreamer', 'readIntoFile',
+           'BufferedStream', 'MD5Stream', 'readStream', 'ProcessStreamer', 'readIntoFile',
            'generatorToStream']
-

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_metafd.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_metafd.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_metafd.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -19,7 +19,7 @@
 Tests for twext.web2.metafd.
 """
 
-from socket import error as SocketError
+from socket import error as SocketError, AF_INET
 from errno import ENOTCONN
 
 from twext.web2 import metafd
@@ -116,9 +116,12 @@
             return "not an fd", "not a description"
         def fakeclose(fd):
             ""
+        def fakegetsockfam(fd):
+            return AF_INET
         self.patch(sendfdport, 'recvfd', fakerecvfd)
         self.patch(sendfdport, 'fromfd', fakefromfd)
         self.patch(sendfdport, 'close', fakeclose)
+        self.patch(sendfdport, 'getsockfam', fakegetsockfam)
         self.patch(metafd, 'InheritedPort', InheritedPortForTesting)
         self.patch(metafd, 'Server', ServerTransportForTesting)
         # This last stubbed out just to prevent dirty reactor warnings.

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_stream.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_stream.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twext/web2/test/test_stream.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -11,6 +11,7 @@
 
 from twisted.python.util import sibpath
 sibpath # sibpath is *not* unused - the doctests use it.
+from twisted.python.hashlib import md5
 from twisted.internet import reactor, defer, interfaces
 from twisted.trial import unittest
 from twext.web2 import stream
@@ -165,40 +166,7 @@
 In the morning glad I see
 My foe outstretch'd beneath the tree"""
 
-class TestSubstream(unittest.TestCase):
 
-    def setUp(self):
-        self.data = testdata
-        self.s = stream.MemoryStream(self.data)
-
-    def suckTheMarrow(self, s):
-        return ''.join(map(str, list(iter(s.read, None))))
-
-    def testStart(self):
-        s = stream.substream(self.s, 0, 11)
-        self.assertEquals('I was angry', self.suckTheMarrow(s))
-
-    def testNotStart(self):
-        s = stream.substream(self.s, 12, 26)
-        self.assertEquals('with my friend', self.suckTheMarrow(s))
-
-    def testReverseStartEnd(self):
-        self.assertRaises(ValueError, stream.substream, self.s, 26, 12)
-
-    def testEmptySubstream(self):
-        s = stream.substream(self.s, 11, 11)
-        self.assertEquals('', self.suckTheMarrow(s))
-
-    def testEnd(self):
-        size = len(self.data)
-        s = stream.substream(self.s, size-4, size)
-        self.assertEquals('tree', self.suckTheMarrow(s))
-
-    def testPastEnd(self):
-        size = len(self.data)
-        self.assertRaises(ValueError, stream.substream, self.s, size-4, size+8)
-
-
 class TestBufferedStream(unittest.TestCase):
 
     def setUp(self):
@@ -615,6 +583,99 @@
     """
 
 
+class AsynchronousDummyStream(object):
+    """
+    An L{IByteStream} implementation which always returns a
+    L{defer.Deferred} from C{read} and lets an external driver fire
+    them.
+    """
+    def __init__(self):
+        self._readResults = []
+
+    def read(self):
+        result = defer.Deferred()
+        self._readResults.append(result)
+        return result
+
+    def _write(self, bytes):
+        self._readResults.pop(0).callback(bytes)
+
+class MD5StreamTest(unittest.TestCase):
+    """
+    Tests for L{stream.MD5Stream}.
+    """
+    data = "I am sorry Dave, I can't do that.\n--HAL 9000"
+    digest = md5(data).hexdigest()
+
+    def test_synchronous(self):
+        """
+        L{stream.MD5Stream} computes the MD5 hash of the contents of the stream
+        around which it is wrapped.  It supports L{IByteStream} providers which
+        return C{str} from their C{read} method.
+        """
+        dataStream = stream.MemoryStream(self.data)
+        md5Stream = stream.MD5Stream(dataStream)
+
+        self.assertEquals(str(md5Stream.read()), self.data)
+        self.assertIdentical(md5Stream.read(), None)
+        md5Stream.close()
+
+        self.assertEquals(self.digest, md5Stream.getMD5())
+
+    def test_asynchronous(self):
+        """
+        L{stream.MD5Stream} also supports L{IByteStream} providers which return
+        L{Deferreds} from their C{read} method.
+        """
+        dataStream = AsynchronousDummyStream()
+        md5Stream = stream.MD5Stream(dataStream)
+
+        result = md5Stream.read()
+        dataStream._write(self.data)
+        result.addCallback(self.assertEquals, self.data)
+
+        def cbRead(ignored):
+            result = md5Stream.read()
+            dataStream._write(None)
+            result.addCallback(self.assertIdentical, None)
+            return result
+        result.addCallback(cbRead)
+
+        def cbClosed(ignored):
+            md5Stream.close()
+            self.assertEquals(md5Stream.getMD5(), self.digest)
+        result.addCallback(cbClosed)
+
+        return result
+
+    def test_getMD5FailsBeforeClose(self):
+        """
+        L{stream.MD5Stream.getMD5} raises L{RuntimeError} if called before
+        L{stream.MD5Stream.close}.
+        """
+        dataStream = stream.MemoryStream(self.data)
+        md5Stream = stream.MD5Stream(dataStream)
+        self.assertRaises(RuntimeError, md5Stream.getMD5)
+
+    def test_initializationFailsWithoutStream(self):
+        """
+        L{stream.MD5Stream.__init__} raises L{ValueError} if passed C{None} as
+        the stream to wrap.
+        """
+        self.assertRaises(ValueError, stream.MD5Stream, None)
+
+    def test_readAfterClose(self):
+        """
+        L{stream.MD5Stream.read} raises L{RuntimeError} if called after
+        L{stream.MD5Stream.close}.
+        """
+        dataStream = stream.MemoryStream(self.data)
+        md5Stream = stream.MD5Stream(dataStream)
+        md5Stream.close()
+        self.assertRaises(RuntimeError, md5Stream.read)
+
+
+
 __doctests__ = ['twext.web2.test.test_stream', 'twext.web2.stream']
 # TODO:
 # CompoundStreamTest

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/customxml.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/customxml.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1120,6 +1120,9 @@
 
     allowed_children = {
         (dav_namespace, "href")                       : (0, 1),
+        (calendarserver_namespace, "common-name")     : (0, 1),
+        (calendarserver_namespace, "first-name")      : (0, 1),
+        (calendarserver_namespace, "last-name")       : (0, 1),
         (calendarserver_namespace, "invite-accepted") : (0, 1),
         (calendarserver_namespace, "invite-declined") : (0, 1),
         (calendarserver_namespace, "hosturl")         : (0, 1),

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/dateops.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/dateops.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/dateops.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,5 +1,5 @@
 ##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2012 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.
@@ -259,9 +259,32 @@
     @return: L{PyCalendarDateTime} result
     """
     
-    dt = datetime.datetime.strptime(ts[:19], "%Y-%m-%d %H:%M:%S")
-    return PyCalendarDateTime(year=dt.year, month=dt.month, day=dt.day, hours=dt.hour, minutes=dt.minute, seconds=dt.second)
+    # Format is "%Y-%m-%d %H:%M:%S"
+    return PyCalendarDateTime(
+        year=int(ts[0:4]),
+        month=int(ts[5:7]),
+        day=int(ts[8:10]),
+        hours=int(ts[11:13]),
+        minutes=int(ts[14:16]),
+        seconds=int(ts[17:19])
+    )
 
+def parseSQLDateToPyCalendar(ts):
+    """
+    Parse an SQL formated date into a PyCalendarDateTime
+    @param ts: the SQL date
+    @type ts: C{str}
+    
+    @return: L{PyCalendarDateTime} result
+    """
+    
+    # Format is "%Y-%m-%d", though Oracle may add zero time which we ignore
+    return PyCalendarDateTime(
+        year=int(ts[0:4]),
+        month=int(ts[5:7]),
+        day=int(ts[8:10])
+    )
+
 def datetimeMktime(dt):
 
     assert isinstance(dt, datetime.date)

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/sudo.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/sudo.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/sudo.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,144 +0,0 @@
-##
-# Copyright (c) 2006-2012 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.
-##
-
-"""
-Directory service implementation for users who are allowed to authorize
-as other principals.
-"""
-
-__all__ = [
-    "SudoDirectoryService",
-]
-
-from twext.python.filepath import CachingFilePath as FilePath
-from twisted.cred.credentials import IUsernamePassword, IUsernameHashedPassword
-from twisted.cred.error import UnauthorizedLogin
-
-from twext.python.plistlib import readPlist
-
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-
-class SudoDirectoryService(DirectoryService):
-    """
-    L{IDirectoryService} implementation for Sudo users.
-    """
-    baseGUID = "1EE00E46-1885-4DBC-A001-590AFA76A8E3"
-
-    realmName = None
-
-    plistFile = None
-
-    recordType_sudoers = "sudoers"
-
-    def __repr__(self):
-        return "<%s %r: %r>" % (self.__class__.__name__, self.realmName,
-                                self.plistFile)
-
-    def __init__(self, plistFile):
-        super(SudoDirectoryService, self).__init__()
-
-        if isinstance(plistFile, (unicode, str)):
-            plistFile = FilePath(plistFile)
-
-        self.plistFile = plistFile
-        self._fileInfo = None
-        self._plist = None
-        self._accounts()
-
-    def _accounts(self):
-        if self._plist is None:
-            self.plistFile.restat()
-            fileInfo = (self.plistFile.getmtime(), self.plistFile.getsize())
-            if fileInfo != self._fileInfo:
-                self._plist = readPlist(self.plistFile.path)
-                self._fileInfo = fileInfo
-
-        return self._plist
-
-    def recordTypes(self):
-        return (SudoDirectoryService.recordType_sudoers,)
-
-    def _recordForEntry(self, entry):
-        return SudoDirectoryRecord(
-            service=self,
-            recordType=SudoDirectoryService.recordType_sudoers,
-            shortName=entry['username'],
-            entry=entry)
-
-
-    def listRecords(self, recordType):
-        if recordType != SudoDirectoryService.recordType_sudoers:
-            raise UnknownRecordTypeError(recordType)
-
-        for entry in self._accounts()['users']:
-            yield self._recordForEntry(entry)
-
-    def recordWithShortName(self, recordType, shortName):
-        if recordType != SudoDirectoryService.recordType_sudoers:
-            raise UnknownRecordTypeError(recordType)
-
-        for entry in self._accounts()['users']:
-            if entry['username'] == shortName:
-                return self._recordForEntry(entry)
-
-    def requestAvatarId(self, credentials):
-        # FIXME: ?
-        # We were checking if principal is enabled; seems unnecessary in current
-        # implementation because you shouldn't have a principal object for a
-        # disabled directory principal.
-
-        if credentials.authnPrincipal is None or not hasattr(credentials.authnPrincipal, "record"):
-            raise UnauthorizedLogin("No such user: %s" % (credentials.credentials.username,))
-        sudouser = credentials.authnPrincipal.record
-
-        if credentials.authnPrincipal.record.verifyCredentials(credentials.credentials):
-            return (
-                credentials.authnPrincipal.principalURL(),
-                credentials.authzPrincipal.principalURL(),
-                credentials.authnPrincipal,
-                credentials.authzPrincipal,
-                )
-        else:
-            raise UnauthorizedLogin(
-                "Incorrect credentials for %s" % (sudouser,))
-
-
-class SudoDirectoryRecord(DirectoryRecord):
-    """
-    L{DirectoryRecord} implementation for Sudo users.
-    """
-
-    def __init__(self, service, recordType, shortName, entry):
-        super(SudoDirectoryRecord, self).__init__(
-            service=service,
-            recordType=recordType,
-            uid="%s:%s" % (recordType, shortName),
-            shortNames=(shortName,),
-            fullName=shortName,
-        )
-
-        self.password = entry['password']
-
-        self.enabled = True     # Explicitly enabled
-
-    def verifyCredentials(self, credentials):
-        if IUsernamePassword.providedBy(credentials):
-            return credentials.checkPassword(self.password)
-        elif IUsernameHashedPassword.providedBy(credentials):
-            return credentials.checkPassword(self.password)
-
-        return super(SudoDirectoryRecord, self).verifyCredentials(credentials)

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/resources/caldavd.plist
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/resources/caldavd.plist	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/resources/caldavd.plist	2012-04-13 20:13:22 UTC (rev 9107)
@@ -284,10 +284,6 @@
       <!-- <string>/principals/__uids__/983C8238-FB6B-4D92-9242-89C0A39E5F81/</string> -->
     </array>
 
-    <!-- Principals that can pose as other principals -->
-    <key>SudoersFile</key>
-    <string>conf/sudoers.plist</string>
-
     <!-- Create "proxy access" principals -->
     <key>EnableProxyPrincipals</key>
     <true/>

Deleted: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/test_sudo.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/test_sudo.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/test/test_sudo.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1,75 +0,0 @@
-##
-# Copyright (c) 2005-2009 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 os
-
-from twext.python.filepath import CachingFilePath as FilePath
-
-import twistedcaldav.directory.test.util
-from twistedcaldav.directory.sudo import SudoDirectoryService
-
-plistFile = FilePath(os.path.join(os.path.dirname(__file__), "sudoers.plist"))
-plistFile2 = FilePath(os.path.join(os.path.dirname(__file__), "sudoers2.plist"))
-
-class SudoTestCase(
-    twistedcaldav.directory.test.util.BasicTestCase,
-    twistedcaldav.directory.test.util.DigestTestCase
-):
-    """
-    Test the Sudo Directory Service
-    """
-
-    recordTypes = set(('sudoers',))
-    recordType = 'sudoers'
-
-    sudoers = {
-        'alice': {'password': 'alice',},
-    }
-
-    locations = {}
-
-    def plistFile(self):
-        if not hasattr(self, "_plistFile"):
-            self._plistFile = FilePath(self.mktemp())
-            plistFile.copyTo(self._plistFile)
-        return self._plistFile
-
-    def service(self):
-        service = SudoDirectoryService(self.plistFile())
-        service.realmName = "test realm"
-        return service
-
-    def test_listRecords(self):
-        for record in self.service().listRecords(self.recordType):
-            self.failUnless(record.shortNames[0] in self.sudoers)
-            self.assertEqual(self.sudoers[record.shortNames[0]]['password'],
-                             record.password)
-
-    def test_recordWithShortName(self):
-        service = self.service()
-
-        record = service.recordWithShortName(self.recordType, 'alice')
-        self.assertEquals(record.password, 'alice')
-
-        record = service.recordWithShortName(self.recordType, 'bob')
-        self.failIf(record)
-
-    def test_calendaringDisabled(self):
-        service = self.service()
-
-        record = service.recordWithShortName(self.recordType, 'alice')
-
-        self.failIf(record.enabledForCalendaring,
-                    "sudoers should have enabledForCalendaring=False")

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/directory/xmlfile.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -30,6 +30,7 @@
 from twext.web2.auth.digest import DigestedCredentials
 from twext.python.filepath import CachingFilePath as FilePath
 from twistedcaldav.config import config
+from twisted.internet.defer import succeed
 
 from twistedcaldav.config import fullServerPath
 from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, DirectoryError
@@ -38,7 +39,6 @@
 from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
 from twistedcaldav.xmlutil import addSubElement, createElement, elementToXML
 from uuid import uuid4
-from twisted.internet.defer import succeed
 
 
 class XMLDirectoryService(DirectoryService):
@@ -338,7 +338,7 @@
             recordTypes = list(self.recordTypes())
         else:
             recordTypes = (recordType,)
-        
+
         records = []
         for recordType in recordTypes:
             for xmlPrincipal in self._accounts()[recordType].itervalues():
@@ -348,8 +348,9 @@
                     record = self.recordWithGUID(xmlPrincipal.guid)
                     if record:
                         records.append(record)
-        return succeed(records) 
+        return succeed(records)
 
+
     def _addElement(self, parent, principal):
         """
         Create an XML element from principal and add it as a child of parent

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/extensions.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/extensions.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -35,13 +35,11 @@
 
 from twisted.internet.defer import succeed, maybeDeferred
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.cred.error import LoginFailed, UnauthorizedLogin
 
 from twisted.web.template import Element, XMLFile, renderer, tags, flattenString
 from twisted.python.modules import getModule
 
 from twext.web2 import responsecode, server
-from twext.web2.auth.wrapper import UnauthorizedResponse
 from twext.web2.http import HTTPError, Response, RedirectResponse
 from twext.web2.http import StatusResponse
 from twext.web2.http_headers import MimeType
@@ -49,9 +47,7 @@
 from twext.web2.static import MetaDataMixin, StaticRenderMixin
 from txdav.xml import element
 from txdav.xml.element import dav_namespace
-from twext.web2.dav.auth import PrincipalCredentials
 from twext.web2.dav.http import MultiStatusResponse
-from twext.web2.dav.idav import IDAVPrincipalResource
 from twext.web2.dav.static import DAVFile as SuperDAVFile
 from twext.web2.dav.resource import DAVResource as SuperDAVResource
 from twext.web2.dav.resource import (
@@ -65,8 +61,6 @@
 from twistedcaldav import customxml
 from twistedcaldav.customxml import calendarserver_namespace
 
-from twistedcaldav.directory.sudo import SudoDirectoryService
-from twistedcaldav.directory.directory import DirectoryService
 from twistedcaldav.method.report import http_REPORT
 
 from twistedcaldav.config import config
@@ -77,208 +71,6 @@
 log = Logger()
 
 
-class SudoersMixin (object):
-    """
-    Mixin class to let DAVResource, and DAVFile subclasses know about
-    sudoer principals and how to find their AuthID.
-    """
-
-    @inlineCallbacks
-    def authenticate(self, request):
-        # Bypass normal authentication if its already been done (by SACL check)
-        if (
-            hasattr(request, "authnUser") and
-            hasattr(request, "authzUser") and
-            request.authnUser is not None and
-            request.authzUser is not None
-        ):
-            returnValue((request.authnUser, request.authzUser))
-
-        # Copy of SuperDAVResource.authenticate except we pass the
-        # creds on as well as we will need to take different actions
-        # based on what the auth method was
-        if not (
-            hasattr(request, "portal") and 
-            hasattr(request, "credentialFactories") and
-            hasattr(request, "loginInterfaces")
-        ):
-            request.authnUser = element.Principal(element.Unauthenticated())
-            request.authzUser = element.Principal(element.Unauthenticated())
-            returnValue((request.authnUser, request.authzUser,))
-
-        authHeader = request.headers.getHeader("authorization")
-
-        if authHeader is not None:
-            if authHeader[0] not in request.credentialFactories:
-                log.error("Client authentication scheme %s is not provided by server %s"
-                               % (authHeader[0], request.credentialFactories.keys()))
-
-                response = (yield UnauthorizedResponse.makeResponse(
-                    request.credentialFactories,
-                    request.remoteAddr
-                ))
-                raise HTTPError(response)
-            else:
-                factory = request.credentialFactories[authHeader[0]]
-
-                try:
-                    creds = (yield factory.decode(authHeader[1], request))
-                except (UnauthorizedLogin, LoginFailed,):
-                    raise HTTPError((yield UnauthorizedResponse.makeResponse(
-                                request.credentialFactories, request.remoteAddr)))
-
-                # Try to match principals in each principal collection on the resource
-                authnPrincipal, authzPrincipal = (yield self.principalsForAuthID(request, creds))
-                if (authnPrincipal, authzPrincipal) == (None, None):
-                    log.info("Could not find the principal resource for user id: %s" % (creds.username,))
-                    raise HTTPError(responsecode.UNAUTHORIZED)
-                    
-                authnPrincipal = IDAVPrincipalResource(authnPrincipal)
-                authzPrincipal = IDAVPrincipalResource(authzPrincipal)
-
-                pcreds = PrincipalCredentials(authnPrincipal, authzPrincipal, creds)
-
-                try:
-                    result = (yield request.portal.login(pcreds, None, *request.loginInterfaces))
-                except UnauthorizedLogin:
-                    raise HTTPError((yield UnauthorizedResponse.makeResponse(
-                                request.credentialFactories, request.remoteAddr)))
-                request.authnUser = result[1]
-                request.authzUser = result[2]
-                returnValue((request.authnUser, request.authzUser,))
-        else:
-            if (
-                hasattr(request, "checkedWiki") and
-                hasattr(request, "authnUser") and
-                hasattr(request, "authzUser")
-            ):
-                # This request has already been authenticated via the wiki
-                returnValue((request.authnUser, request.authzUser))
-
-            request.authnUser = element.Principal(element.Unauthenticated())
-            request.authzUser = element.Principal(element.Unauthenticated())
-            returnValue((request.authnUser, request.authzUser,))
-
-    def principalsForAuthID(self, request, creds):
-        """
-        Return authentication and authorization prinicipal identifiers
-        for the authentication identifer passed in. In this
-        implementation authn and authz principals are the same.
-
-        @param request: the L{IRequest} for the request in progress.
-        @param creds: L{Credentials} or the principal to lookup.
-        @return: a deferred tuple of two tuples. Each tuple is
-            C{(principal, principalURI)} where: C{principal} is the
-            L{Principal} that is found; {principalURI} is the C{str}
-            URI of the principal.  The first tuple corresponds to
-            authentication identifiers, the second to authorization
-            identifiers.  It will errback with an
-            HTTPError(responsecode.FORBIDDEN) if the principal isn't
-            found.
-        """
-        authnPrincipal = self.findPrincipalForAuthID(creds)
-
-        if authnPrincipal is None:
-            return succeed((None, None))
-
-        d = self.authorizationPrincipal(request, creds.username, authnPrincipal)
-        d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
-        return d
-
-    def findPrincipalForAuthID(self, creds):
-        """
-        Return an authentication and authorization principal
-        identifiers for the authentication identifier passed in.
-        Check for sudo users before regular users.
-        """
-        if type(creds) is str:
-            return super(SudoersMixin, self).findPrincipalForAuthID(creds)
-
-        for collection in self.principalCollections():
-            principal = collection.principalForShortName(
-                SudoDirectoryService.recordType_sudoers, 
-                creds.username)
-            if principal is not None:
-                return principal
-
-        for collection in self.principalCollections():
-            principal = collection.principalForAuthID(creds)
-            if principal is not None:
-                return principal
-        return None
-
-    @inlineCallbacks
-    def authorizationPrincipal(self, request, authID, authnPrincipal):
-        """
-        Determine the authorization principal for the given request
-        and authentication principal.  This implementation looks for
-        an X-Authorize-As header value to use as the authorization
-        principal.
-        
-        @param request: the L{IRequest} for the request in progress.
-        @param authID: a string containing the
-            authentication/authorization identifier for the principal
-            to lookup.
-        @param authnPrincipal: the L{IDAVPrincipal} for the
-            authenticated principal
-        @return: a deferred result C{tuple} of (L{IDAVPrincipal},
-            C{str}) containing the authorization principal resource
-            and URI respectively.
-        """
-        # Look for X-Authorize-As Header
-        authz = request.headers.getRawHeaders("x-authorize-as")
-
-        if authz is not None and (len(authz) == 1):
-            # Substitute the authz value for principal look up
-            authz = authz[0]
-
-        def getPrincipalForType(type, name):
-            for collection in self.principalCollections():
-                principal = collection.principalForShortName(type, name)
-                if principal:
-                    return principal
-
-        def isSudoUser(authzID):
-            if getPrincipalForType(SudoDirectoryService.recordType_sudoers, authzID):
-                return True
-            return False
-
-        if (
-            hasattr(authnPrincipal, "record") and
-            authnPrincipal.record.recordType == SudoDirectoryService.recordType_sudoers
-        ):
-            if authz:
-                if isSudoUser(authz):
-                    log.info("Cannot proxy as another proxy: user %r as user %r"
-                             % (authID, authz))
-                    raise HTTPError(responsecode.FORBIDDEN)
-                else:
-                    authzPrincipal = getPrincipalForType(DirectoryService.recordType_users, authz)
-
-                    if not authzPrincipal:
-                        authzPrincipal = self.findPrincipalForAuthID(authz)
-
-                    if authzPrincipal is not None:
-                        log.info("Allow proxy: user %r as %r"
-                                 % (authID, authz,))
-                        returnValue(authzPrincipal)
-                    else:
-                        log.info("Could not find authorization user id: %r"
-                                 % (authz,))
-                        raise HTTPError(responsecode.FORBIDDEN)
-            else:
-                log.info("Cannot authenticate proxy user %r without X-Authorize-As header"
-                         % (authID,))
-                raise HTTPError(responsecode.BAD_REQUEST)
-        elif authz:
-            log.info("Cannot proxy: user %r as %r" % (authID, authz,))
-            raise HTTPError(responsecode.FORBIDDEN)
-        else:
-            # No proxy - do default behavior
-            result = (yield super(SudoersMixin, self).authorizationPrincipal(request, authID, authnPrincipal))
-            returnValue(result)
-
-
 class DirectoryPrincipalPropertySearchMixIn(object):
 
     @inlineCallbacks
@@ -673,7 +465,7 @@
     return wrapper
 
 class DAVResource (DirectoryPrincipalPropertySearchMixIn,
-                   SudoersMixin, SuperDAVResource, LoggingMixIn,
+                   SuperDAVResource, LoggingMixIn,
                    DirectoryRenderingMixIn, StaticRenderMixin):
     """
     Extended L{twext.web2.dav.resource.DAVResource} implementation.
@@ -888,8 +680,7 @@
 
 
 
-class DAVFile (SudoersMixin, SuperDAVFile, LoggingMixIn,
-               DirectoryRenderingMixIn):
+class DAVFile (SuperDAVFile, LoggingMixIn, DirectoryRenderingMixIn):
     """
     Extended L{twext.web2.dav.static.DAVFile} implementation.
     """

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/ical.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/ical.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -148,7 +148,7 @@
     """
     iCalendar Property
     """
-    def __init__(self, name, value, params={}, **kwargs):
+    def __init__(self, name, value, params={}, parent=None, **kwargs):
         """
         @param name: the property's name
         @param value: the property's value
@@ -171,7 +171,9 @@
             for attrname, attrvalue in params.items():
                 self._pycalendar.addAttribute(PyCalendarAttribute(attrname, attrvalue))
 
-    def __str__ (self): return str(self._pycalendar)
+        self._parent = parent
+
+    def __str__(self): return str(self._pycalendar)
     def __repr__(self): return "<%s: %r: %r>" % (self.__class__.__name__, self.name(), self.value())
 
     def __hash__(self):
@@ -200,16 +202,23 @@
         Duplicate this object and all its contents.
         @return: the duplicated calendar.
         """
+        # FIXME: does the parent need to be set in this case?
         return Property(None, None, None, pycalendar=self._pycalendar.duplicate())
-        
-    def name  (self): return self._pycalendar.getName()
 
-    def value (self): return self._pycalendar.getValue().getValue()
+    def name(self): return self._pycalendar.getName()
 
-    def strvalue (self): return str(self._pycalendar.getValue())
+    def value(self): return self._pycalendar.getValue().getValue()
 
+    def strvalue(self): return str(self._pycalendar.getValue())
+
+    def _markAsDirty(self):
+        parent = getattr(self, "_parent", None)
+        if parent is not None:
+            parent._markAsDirty()
+
     def setValue(self, value):
         self._pycalendar.setValue(value)
+        self._markAsDirty()
 
     def parameterNames(self):
         """
@@ -236,12 +245,15 @@
 
     def setParameter(self, paramname, paramvalue):
         self._pycalendar.replaceAttribute(PyCalendarAttribute(paramname, paramvalue))
+        self._markAsDirty()
 
     def removeParameter(self, paramname):
         self._pycalendar.removeAttributes(paramname)
+        self._markAsDirty()
 
     def removeAllParameters(self):
         self._pycalendar.setAttributes({})
+        self._markAsDirty()
 
     def removeParameterValue(self, paramname, paramvalue):
 
@@ -253,6 +265,7 @@
                         if value == paramvalue:
                             if not attr.removeValue(value):
                                 self._pycalendar.removeAttributes(paramname)
+        self._markAsDirty()
 
     def containsTimeRange(self, start, end, defaulttz=None):
         """
@@ -416,8 +429,6 @@
             else:
                 raise AssertionError("name may not be None")
 
-            # FIXME: _parent is not use internally, and appears to be used elsewhere,
-            # even though it's names as a private variable.
             if "parent" in kwargs:
                 parent = kwargs["parent"]
                 
@@ -433,12 +444,25 @@
             self._pycalendar = PyCalendar(add_defaults=False) if name == "VCALENDAR" else PyCalendar.makeComponent(name, None)
             self._parent = None
 
-    def __str__ (self):
+    def __str__(self):
         """
         NB This does not automatically include timezones in VCALENDAR objects.
         """
-        return str(self._pycalendar)
+        cachedCopy = getattr(self, "_cachedCopy", None)
+        if cachedCopy is not None:
+            return cachedCopy
+        self._cachedCopy = str(self._pycalendar)
+        return self._cachedCopy
 
+    def _markAsDirty(self):
+        """
+        Invalidate the cached copy of serialized icalendar data
+        """
+        self._cachedCopy = None
+        parent = getattr(self, "_parent", None)
+        if parent is not None:
+            parent._markAsDirty()
+
     def __repr__(self):
         return "<%s: %r>" % (self.__class__.__name__, str(self._pycalendar))
 
@@ -590,6 +614,7 @@
         """
         self._pycalendar.addComponent(component._pycalendar)
         component._parent = self
+        self._markAsDirty()
 
     def removeComponent(self, component):
         """
@@ -597,6 +622,8 @@
         @param component: the L{Component} to remove.
         """
         self._pycalendar.removeComponent(component._pycalendar)
+        component._parent = None
+        self._markAsDirty()
 
     def hasProperty(self, name):
         """
@@ -631,7 +658,7 @@
             properties = self._pycalendar.getProperties(name)
 
         return (
-            Property(None, None, None, pycalendar=p)
+            Property(None, None, None, parent=self, pycalendar=p)
             for p in properties
         )
 
@@ -807,6 +834,8 @@
         """
         self._pycalendar.addProperty(property._pycalendar)
         self._pycalendar.finalise()
+        property._parent = self
+        self._markAsDirty()
 
     def removeProperty(self, property):
         """
@@ -815,6 +844,8 @@
         """
         self._pycalendar.removeProperty(property._pycalendar)
         self._pycalendar.finalise()
+        property._parent = None
+        self._markAsDirty()
 
     def replaceProperty(self, property):
         """
@@ -825,6 +856,7 @@
         # Remove all existing ones first
         self._pycalendar.removeProperties(property.name())
         self.addProperty(property)
+        self._markAsDirty()
 
     def timezoneIDs(self):
         """
@@ -910,6 +942,8 @@
                             rrules.changed()
                             changed = True
 
+        if changed:
+            self._markAsDirty()
         return changed
 
     def expand(self, start, end, timezone=None):

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/method/put_common.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/method/put_common.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -208,44 +208,48 @@
         self.validIfScheduleMatch()
 
         if self.destinationcal:
-            # Valid resource name check
-            result, message = self.validResourceName()
-            if not result:
-                log.err(message)
-                raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
 
-            # Valid collection size check on the destination parent resource
-            result, message = (yield self.validCollectionSize())
-            if not result:
-                log.err(message)
-                raise HTTPError(ErrorResponse(
-                    responsecode.FORBIDDEN,
-                    customxml.MaxResources(),
-                    "Too many resources in collection",
-                ))
+            # Skip validation on internal requests
+            if not self.internal_request:
 
-            # Valid data sizes - do before parsing the data
-            if self.source is not None:
-                # Valid content length check on the source resource
-                result, message = self.validContentLength()
+                # Valid resource name check
+                result, message = self.validResourceName()
                 if not result:
                     log.err(message)
-                    raise HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, "max-resource-size"),
-                        "Calendar data too large",
-                    ))
-            else:
-                # Valid calendar data size check
-                result, message = self.validSizeCheck()
+                    raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Resource name not allowed"))
+
+                # Valid collection size check on the destination parent resource
+                result, message = (yield self.validCollectionSize())
                 if not result:
                     log.err(message)
                     raise HTTPError(ErrorResponse(
                         responsecode.FORBIDDEN,
-                        (caldav_namespace, "max-resource-size"),
-                        "Calendar data too large",
+                        customxml.MaxResources(),
+                        "Too many resources in collection",
                     ))
 
+                # Valid data sizes - do before parsing the data
+                if self.source is not None:
+                    # Valid content length check on the source resource
+                    result, message = self.validContentLength()
+                    if not result:
+                        log.err(message)
+                        raise HTTPError(ErrorResponse(
+                            responsecode.FORBIDDEN,
+                            (caldav_namespace, "max-resource-size"),
+                            "Calendar data too large",
+                        ))
+                else:
+                    # Valid calendar data size check
+                    result, message = self.validSizeCheck()
+                    if not result:
+                        log.err(message)
+                        raise HTTPError(ErrorResponse(
+                            responsecode.FORBIDDEN,
+                            (caldav_namespace, "max-resource-size"),
+                            "Calendar data too large",
+                        ))
+
             if not self.sourcecal:
                 # Valid content type check on the source resource if its not in a calendar collection
                 if self.source is not None:
@@ -286,51 +290,54 @@
                     if self.calendar.stripKnownTimezones():
                         self.calendardata = None
 
-                # Valid calendar data check
-                result, message = self.validCalendarDataCheck()
-                if not result:
-                    log.err(message)
-                    raise HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, "valid-calendar-data"),
-                        description=message
-                    ))
-                    
-                # Valid calendar data for CalDAV check
-                result, message = self.validCalDAVDataCheck()
-                if not result:
-                    log.err(message)
-                    raise HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, "valid-calendar-object-resource"),
-                        "Invalid calendar data",
-                    ))
+                # Skip validation on internal requests
+                if not self.internal_request:
 
-                # Valid calendar component for check
-                result, message = self.validComponentType()
-                if not result:
-                    log.err(message)
-                    raise HTTPError(ErrorResponse(
-                        responsecode.FORBIDDEN,
-                        (caldav_namespace, "supported-component"),
-                        "Invalid calendar data",
-                    ))
+                    # Valid calendar data check
+                    result, message = self.validCalendarDataCheck()
+                    if not result:
+                        log.err(message)
+                        raise HTTPError(ErrorResponse(
+                            responsecode.FORBIDDEN,
+                            (caldav_namespace, "valid-calendar-data"),
+                            description=message
+                        ))
 
-                # Valid attendee list size check
-                result, message = (yield self.validAttendeeListSizeCheck())
-                if not result:
-                    log.err(message)
-                    raise HTTPError(
-                        ErrorResponse(
+                    # Valid calendar data for CalDAV check
+                    result, message = self.validCalDAVDataCheck()
+                    if not result:
+                        log.err(message)
+                        raise HTTPError(ErrorResponse(
                             responsecode.FORBIDDEN,
-                            MaxAttendeesPerInstance.fromString(str(config.MaxAttendeesPerInstance)),
-                            "Too many attendees in calendar data",
+                            (caldav_namespace, "valid-calendar-object-resource"),
+                            "Invalid calendar data",
+                        ))
+
+                    # Valid calendar component for check
+                    result, message = self.validComponentType()
+                    if not result:
+                        log.err(message)
+                        raise HTTPError(ErrorResponse(
+                            responsecode.FORBIDDEN,
+                            (caldav_namespace, "supported-component"),
+                            "Invalid calendar data",
+                        ))
+
+                    # Valid attendee list size check
+                    result, message = (yield self.validAttendeeListSizeCheck())
+                    if not result:
+                        log.err(message)
+                        raise HTTPError(
+                            ErrorResponse(
+                                responsecode.FORBIDDEN,
+                                MaxAttendeesPerInstance.fromString(str(config.MaxAttendeesPerInstance)),
+                                "Too many attendees in calendar data",
+                            )
                         )
-                    )
 
-                # Normalize the calendar user addresses once we know we have valid
-                # calendar data
-                self.destination.iCalendarAddressDoNormalization(self.calendar)
+                    # Normalize the calendar user addresses once we know we have valid
+                    # calendar data
+                    self.destination.iCalendarAddressDoNormalization(self.calendar)
 
                 # Must have a valid UID at this point
                 self.uid = self.calendar.resourceUID()
@@ -555,6 +562,7 @@
         result = True
         message = ""
         if config.MaxResourceSize:
+            # FIXME PERF could be done more efficiently?
             calsize = len(str(self.calendar))
             if calsize > config.MaxResourceSize:
                 result = False
@@ -1052,9 +1060,14 @@
     def doStorePut(self, data=None):
 
         if data is None:
+            # We'll be passing this component directly to storeComponent( )
+            componentToStore = self.calendar
             if self.calendardata is None:
                 self.calendardata = str(self.calendar)
             data = self.calendardata
+        else:
+            # We'll be passing data as a stream to storeStream( )
+            componentToStore = None
 
         # Update calendar-access property value on the resource. We need to do this before the
         # store as the store will "commit" the new value.
@@ -1116,8 +1129,12 @@
             self.destination.scheduleEtags = ()                
 
 
-        stream = MemoryStream(data)
-        response = yield self.destination.storeStream(stream)
+        if componentToStore is None:
+            stream = MemoryStream(data)
+            response = yield self.destination.storeStream(stream)
+        else:
+            # Since we already have a component, we can pass it directly
+            response = yield self.destination.storeComponent(componentToStore)
         response = IResponse(response)
 
         if self.isScheduleResource:

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/scheduling/scheduler.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/scheduling/scheduler.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -905,12 +905,20 @@
         For data coming in from outside we need to normalize the calendar user addresses so that later iTIP
         processing will match calendar users against those in stored calendar data. Only do that for invites
         not freebusy.
+        
+        We also need to apply recurrence truncation here so the incoming iTIP's RRULE matches the truncated
+        version in any attendee calendar. This avoids a problem where the iTIP message appears to represent
+        a significant change as reported in schedule-changes property.
         """
 
         if not self.checkForFreeBusy():
             self.calendar.normalizeCalendarUserAddresses(normalizationLookup,
                 self.resource.principalForCalendarUserAddress)
 
+        # Apply recurrence truncation at this point
+        if config.MaxInstancesForRRULE != 0:
+            self.calendar.truncateRecurrence(config.MaxInstancesForRRULE)        
+
     def checkAuthorization(self):
         # Must have an unauthenticated user
         if self.resource.currentPrincipal(self.request) != davxml.Principal(davxml.Unauthenticated()):

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/sharing.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/sharing.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -313,7 +313,11 @@
         if inviteAccess in ("read-write", "read-write-schedule",):
             userprivs.append(element.Privilege(element.Write()))
         proxyprivs = list(userprivs)
-        proxyprivs.remove(element.Privilege(element.ReadACL()))
+        try:
+            proxyprivs.remove(element.Privilege(element.ReadACL()))
+        except ValueError:
+            # If wiki says no-access then ReadACL won't be in the list
+            pass
 
         aces = (
             # Inheritable specific access for the resource's associated principal.
@@ -1188,8 +1192,8 @@
         """
         
         # Change state in sharer invite
-        owner = (yield self.ownerPrincipal(request))
-        owner = owner.principalURL()
+        ownerPrincipal = (yield self.ownerPrincipal(request))
+        owner = ownerPrincipal.principalURL()
         sharedCollection = (yield request.locateResource(hostUrl))
         if sharedCollection is None:
             # Original shared collection is gone - nothing we can do except ignore it
@@ -1202,30 +1206,44 @@
         # Change the record
         yield sharedCollection.changeUserInviteState(request, replytoUID, owner, state, displayname)
 
-        yield self.sendReply(request, owner, sharedCollection, state, hostUrl, replytoUID, displayname)
+        yield self.sendReply(request, ownerPrincipal, sharedCollection, state, hostUrl, replytoUID, displayname)
 
     @inlineCallbacks
-    def sendReply(self, request, sharee, sharedCollection, state, hostUrl, replytoUID, displayname=None):
+    def sendReply(self, request, shareePrincipal, sharedCollection, state, hostUrl, replytoUID, displayname=None):
 
         # Locate notifications collection for sharer
         sharer = (yield sharedCollection.ownerPrincipal(request))
         notifications = (yield request.locateResource(sharer.notificationURL()))
-        
+
         # Generate invite XML
         notificationUID = "%s-reply" % (replytoUID,)
         xmltype = customxml.InviteReply()
+
+        # Prefer mailto:, otherwise use principal URL
+        for cua in shareePrincipal.calendarUserAddresses():
+            if cua.startswith("mailto:"):
+                break
+        else:
+            cua = shareePrincipal.principalURL()
+
+        commonName = shareePrincipal.displayName()
+        record = shareePrincipal.record
+
         xmldata = customxml.Notification(
             customxml.DTStamp.fromString(PyCalendarDateTime.getNowUTC().getText()),
             customxml.InviteReply(
                 *(
                     (
-                        element.HRef.fromString(sharee),
+                        element.HRef.fromString(cua),
                         inviteStatusMapToXML[state](),
                         customxml.HostURL(
                             element.HRef.fromString(hostUrl),
                         ),
                         customxml.InReplyTo.fromString(replytoUID),
                     ) + ((customxml.InviteSummary.fromString(displayname),) if displayname is not None else ())
+                      + ((customxml.CommonName.fromString(commonName),) if commonName is not None else ())
+                      + ((customxml.FirstNameProperty(record.firstName),) if record.firstName is not None else ())
+                      + ((customxml.LastNameProperty(record.lastName),) if record.lastName is not None else ())
                 )
             ),
         ).toxml()

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/stdconfig.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -420,7 +420,6 @@
     #
     "AdminPrincipals": [],                       # Principals with "DAV:all" access (relative URLs)
     "ReadPrincipals": [],                        # Principals with "DAV:read" access (relative URLs)
-    "SudoersFile": "sudoers.plist",              # Principals that can pose as other principals
     "EnableProxyPrincipals": True,               # Create "proxy access" principals
 
     #
@@ -732,7 +731,6 @@
                 "Service" : "calendarserver.push.applepush.ApplePushNotifierService",
                 "Enabled" : False,
                 "SubscriptionURL" : "apns",
-                "AuthMechanisms" : [],
                 "DataHost" : "",
                 "ProviderHost" : "gateway.push.apple.com",
                 "ProviderPort" : 2195,
@@ -740,6 +738,8 @@
                 "FeedbackPort" : 2196,
                 "FeedbackUpdateSeconds" : 28800, # 8 hours
                 "Environment" : "PRODUCTION",
+                "EnableStaggering" : False,
+                "StaggerSeconds" : 3,
                 "CalDAV" : {
                     "CertificatePath" : "",
                     "PrivateKeyPath" : "",
@@ -920,6 +920,12 @@
         ],
     },
 
+    "QueryCaching" : {
+        "Enabled" : True,
+        "MemcachedPool" : "Default",
+        "ExpireSeconds" : 3600,
+    },
+
     "GroupCaching" : {
         "Enabled": True,
         "MemcachedPool" : "Default",
@@ -930,6 +936,12 @@
         "UseExternalProxies" : False,
     },
 
+    "Manhole": {
+        "Enabled": False,
+        "StartingPortNumber": 5000,
+        "PasswordFilePath": "",
+    },
+
     "EnableKeepAlive": False,
 
     "EnableResponseCache":  True,
@@ -1325,17 +1337,6 @@
             if service["DataHost"] == "":
                 service["DataHost"] = configDict.ServerHostName
 
-            # Advertise Basic and/or Digest on subscription resource
-            if not service["AuthMechanisms"]:
-                authMechanisms = []
-                if configDict.Authentication.Basic.Enabled:
-                    authMechanisms.append("basic")
-                if configDict.Authentication.Digest.Enabled:
-                    authMechanisms.append("digest")
-                if not authMechanisms:
-                    raise ConfigurationError("Must have either 'basic' or 'digest' enabled for Apple Push Notifications.")
-                service["AuthMechanisms"] = authMechanisms
-
             # Retrieve APN topics from certificates if not explicitly set
             for protocol, accountName in (
                 ("CalDAV", "apns:com.apple.calendar"),

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/storebridge.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/storebridge.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -1762,7 +1762,24 @@
 
             returnValue(CREATED)
 
+    @inlineCallbacks
+    def storeComponent(self, component):
 
+        if self._newStoreObject:
+            yield self._newStoreObject.setComponent(component)
+            returnValue(NO_CONTENT)
+        else:
+            self._newStoreObject = (yield self._newStoreParent.createObjectResourceWithName(
+                self.name(), component, self._metadata
+            ))
+
+            # Re-initialize to get stuff setup again now we have no object
+            self._initializeWithObject(self._newStoreObject, self._newStoreParent)
+
+            returnValue(CREATED)
+
+
+
     @inlineCallbacks
     def storeRemove(self, request, implicitly, where):
         """

Copied: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_dateops.py (from rev 9105, CalendarServer/trunk/twistedcaldav/test/test_dateops.py)
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_dateops.py	                        (rev 0)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_dateops.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -0,0 +1,111 @@
+##
+# Copyright (c) 2005-2012 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 twistedcaldav.test.util
+from twisted.trial.unittest import SkipTest
+from pycalendar.datetime import PyCalendarDateTime
+from twistedcaldav.dateops import parseSQLTimestampToPyCalendar,\
+    parseSQLDateToPyCalendar, parseSQLTimestamp, pyCalendarTodatetime
+import datetime
+import dateutil
+
+class Dateops(twistedcaldav.test.util.TestCase):
+    """
+    dateops.py tests
+    """
+
+    def test_normalizeForIndex(self):
+        raise SkipTest("test unimplemented")
+
+    def test_normalizeToUTC(self):
+        raise SkipTest("test unimplemented")
+
+    def test_floatoffset(self):
+        raise SkipTest("test unimplemented")
+
+    def test_adjustFloatingToTimezone(self):
+        raise SkipTest("test unimplemented")
+
+    def test_compareDateTime(self):
+        raise SkipTest("test unimplemented")
+
+    def test_differenceDateTime(self):
+        raise SkipTest("test unimplemented")
+
+    def test_timeRangesOverlap(self):
+        raise SkipTest("test unimplemented")
+
+    def test_normalizePeriodList(self):
+        raise SkipTest("test unimplemented")
+
+    def test_clipPeriod(self):
+        raise SkipTest("test unimplemented")
+
+    def test_pyCalendarTodatetime(self):
+        """
+        dateops.pyCalendarTodatetime
+        """
+        
+        tests = (
+            (PyCalendarDateTime(2012, 4, 4, 12, 34, 56), datetime.datetime(2012, 4, 4, 12, 34, 56, tzinfo=dateutil.tz.tzutc())),
+            (PyCalendarDateTime(2012, 12, 31), datetime.date(2012, 12, 31)),
+        )
+
+        for pycal, result in tests:
+            self.assertEqual(pyCalendarTodatetime(pycal), result)
+
+    def test_parseSQLTimestamp(self):
+        """
+        dateops.parseSQLTimestamp
+        """
+        
+        tests = (
+            ("2012-04-04 12:34:56", datetime.datetime(2012, 4, 4, 12, 34, 56)),
+            ("2012-12-31 01:01:01", datetime.datetime(2012, 12, 31, 1, 1, 1)),
+        )
+
+        for sqlStr, result in tests:
+            self.assertEqual(parseSQLTimestamp(sqlStr), result)
+
+    def test_parseSQLTimestampToPyCalendar(self):
+        """
+        dateops.parseSQLTimestampToPyCalendar
+        """
+        
+        tests = (
+            ("2012-04-04 12:34:56", PyCalendarDateTime(2012, 4, 4, 12, 34, 56)),
+            ("2012-12-31 01:01:01", PyCalendarDateTime(2012, 12, 31, 1, 1, 1)),
+        )
+
+        for sqlStr, result in tests:
+            self.assertEqual(parseSQLTimestampToPyCalendar(sqlStr), result)
+
+    def test_parseSQLDateToPyCalendar(self):
+        """
+        dateops.parseSQLDateToPyCalendar
+        """
+        
+        tests = (
+            ("2012-04-04", PyCalendarDateTime(2012, 4, 4)),
+            ("2012-12-31 00:00:00", PyCalendarDateTime(2012, 12, 31)),
+        )
+
+        for sqlStr, result in tests:
+            self.assertEqual(parseSQLDateToPyCalendar(sqlStr), result)
+
+    def test_datetimeMktime(self):
+        raise SkipTest("test unimplemented")
+

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_icalendar.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/twistedcaldav/test/test_icalendar.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -6086,3 +6086,88 @@
         self.assertEquals("urn:uuid:buz", prop.value())
         self.assertEquals(prop.parameterValue("CALENDARSERVER-OLD-CUA"),
             "http://example.com/principals/users/buz")
+
+
+    def test_serializationCaching(self):
+
+        data = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:12345-67890
+END:VEVENT
+END:VCALENDAR
+"""
+
+        component = Component.fromString(data)
+
+        str(component) # to serialize and cache
+        self.assertNotEquals(component._cachedCopy, None)
+        component._markAsDirty()
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        self.assertNotEquals(component._cachedCopy, None)
+        prop = Property("PRODID", "FOO")
+        component.addProperty(prop)
+        self.assertEquals(prop._parent, component)
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        self.assertNotEquals(component._cachedCopy, None)
+        retrieved = component.getProperty("PRODID")
+        self.assertEquals(retrieved._parent, component)
+
+        str(component) # to serialize and cache
+        component.removeProperty(prop)
+        self.assertEquals(prop._parent, None)
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        prop2 = Property("PRODID", "BAR")
+        component.replaceProperty(prop2)
+        self.assertEquals(prop2._parent, component)
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        prop2.setValue("BAZ")
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        prop2.setParameter("XYZZY", "PLUGH")
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        prop2.removeParameter("XYZZY")
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        prop2.removeAllParameters()
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        prop2.setParameter("XYZZY", ["PLUGH", "PLUGH2"])
+        str(component) # to serialize and cache
+        prop2.removeParameterValue("XYZZY", "PLUGH2")
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        compData = """BEGIN:VCALENDAR
+VERSION:2.0
+BEGIN:VEVENT
+UID:C31854DA-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
+DTSTART;VALUE=DATE:20020214
+DTEND;VALUE=DATE:20020215
+RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2
+SUMMARY:Valentine's Day
+END:VEVENT
+END:VCALENDAR
+"""
+        str(component) # to serialize and cache
+        [subComponent] = Component.fromString(compData).subcomponents()
+        component.addComponent(subComponent)
+        self.assertEquals(subComponent._parent, component)
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated
+
+        str(component) # to serialize and cache
+        component.removeComponent(subComponent)
+        self.assertEquals(subComponent._parent, None)
+        self.assertEquals(component._cachedCopy, None) # cache is invalidated

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/base/datastore/util.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/base/datastore/util.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/base/datastore/util.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -19,6 +19,8 @@
 Common utility functions for a datastores.
 """
 
+from twistedcaldav.memcacher import Memcacher
+
 _unset = object()
 
 class cached(object):
@@ -45,3 +47,34 @@
             else:
                 return cached
         return inner
+
+
+class QueryCacher(Memcacher):
+    """
+    A Memcacher for the object-with-name query (more to come)
+    """
+
+    def __init__(self, cachePool="Default", cacheExpireSeconds=3600):
+        super(QueryCacher, self).__init__(cachePool, pickle=True)
+        self.cacheExpireSeconds = cacheExpireSeconds
+
+    def set(self, key, value):
+        super(QueryCacher, self).set(key, value, expireTime=self.cacheExpireSeconds)
+
+    def keyForObjectWithName(self, homeResourceID, name):
+        return "objectWithName:%s:%s" % (homeResourceID, name)
+
+    def getObjectWithName(self, homeResourceID, name):
+        key = self.keyForObjectWithName(homeResourceID, name)
+        return self.get(key)
+
+    def setObjectWithName(self, transaction, homeResourceID, name, value):
+        key = self.keyForObjectWithName(homeResourceID, name)
+        transaction.postCommit(lambda:self.set(key, value))
+
+    def invalidateObjectWithName(self, transaction, homeResourceID, name):
+        key = self.keyForObjectWithName(homeResourceID, name)
+        # Invalidate immediately and post-commit in case a calendar was created and deleted
+        # within the same transaction
+        self.delete(key)
+        transaction.postCommit(lambda:self.delete(key))

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/sql.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/sql.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -39,7 +39,7 @@
 from twistedcaldav.caldavxml import ScheduleCalendarTransp, Opaque
 from twistedcaldav.config import config
 from twistedcaldav.dateops import normalizeForIndex, datetimeMktime,\
-    parseSQLTimestamp, pyCalendarTodatetime
+    parseSQLTimestamp, pyCalendarTodatetime, parseSQLDateToPyCalendar
 from twistedcaldav.ical import Component, InvalidICalendarDataError
 from twistedcaldav.instance import InvalidOverriddenInstanceError
 from twistedcaldav.memcacher import Memcacher
@@ -753,15 +753,21 @@
 
 
     @inlineCallbacks
-    def updateDatabase(self, component,
-                       expand_until=None, reCreate=False, inserting=False):
+    def updateDatabase(self, component, expand_until=None, reCreate=False,
+                       inserting=False, txn=None):
         """
-        Update the database tables for the new data being written.
+        Update the database tables for the new data being written. Occasionally we might need to do an update to
+        time-range data via a separate transaction, so we allow that to be passed in. Note that in that case
+        access to the parent resources will not occur in this method, so the queries on the new txn won't depend
+        on any parent objects having the same txn set.
 
         @param component: calendar data to store
         @type component: L{Component}
         """
 
+        # Setup appropriate txn
+        txn = txn if txn is not None else self._txn
+
         # Decide how far to expand based on the component
         doInstanceIndexing = False
         master = component.masterComponent()
@@ -774,7 +780,7 @@
         else:
 
             # If migrating or re-creating or config option for delayed indexing is off, always index
-            if reCreate or self._txn._migrating or not config.FreeBusyIndexDelayedExpand:
+            if reCreate or txn._migrating or not config.FreeBusyIndexDelayedExpand:
                 doInstanceIndexing = True
 
             # Duration into the future through which recurrences are expanded in the index
@@ -812,7 +818,7 @@
             self.log_error("Invalid instance %s when indexing %s in %s" %
                            (e.rid, self._name, self._calendar,))
 
-            if self._txn._migrating:
+            if txn._migrating:
                 # TODO: fix the data here by re-writing component then re-index
                 instances = component.expandTimeRanges(expand, ignoreInvalidInstances=True)
                 recurrenceLimit = instances.limit
@@ -843,7 +849,7 @@
             self._size = len(componentText)
 
             # Special - if migrating we need to preserve the original md5
-            if self._txn._migrating and hasattr(component, "md5"):
+            if txn._migrating and hasattr(component, "md5"):
                 self._md5 = component.md5
 
             # Determine attachment mode (ignore inbox's) - NB we have to do this
@@ -885,7 +891,7 @@
                     yield Insert(
                         values,
                         Return=(co.RESOURCE_ID, co.CREATED, co.MODIFIED)
-                    ).on(self._txn)
+                    ).on(txn)
                 )[0]
             else:
                 values[co.MODIFIED] = utcNowSQL
@@ -893,13 +899,13 @@
                     yield Update(
                         values, Return=co.MODIFIED,
                         Where=co.RESOURCE_ID == self._resourceID
-                    ).on(self._txn)
+                    ).on(txn)
                 )[0][0]
                 # Need to wipe the existing time-range for this and rebuild
                 yield Delete(
                     From=tr,
                     Where=tr.CALENDAR_OBJECT_RESOURCE_ID == self._resourceID
-                ).on(self._txn)
+                ).on(txn)
         else:
             values = {
                 co.RECURRANCE_MAX :
@@ -909,13 +915,13 @@
             yield Update(
                 values,
                 Where=co.RESOURCE_ID == self._resourceID
-            ).on(self._txn)
+            ).on(txn)
 
             # Need to wipe the existing time-range for this and rebuild
             yield Delete(
                 From=tr,
                 Where=tr.CALENDAR_OBJECT_RESOURCE_ID == self._resourceID
-            ).on(self._txn)
+            ).on(txn)
 
         if doInstanceIndexing:
             # TIME_RANGE table update
@@ -938,14 +944,14 @@
                             instance.component.getFBType(),
                             icalfbtype_to_indexfbtype["FREE"]),
                     tr.TRANSPARENT                 : transp,
-                }, Return=tr.INSTANCE_ID).on(self._txn))[0][0]
+                }, Return=tr.INSTANCE_ID).on(txn))[0][0]
                 peruserdata = component.perUserTransparency(instance.rid)
                 for useruid, transp in peruserdata:
                     (yield Insert({
                         tpy.TIME_RANGE_INSTANCE_ID : instanceid,
                         tpy.USER_ID                : useruid,
                         tpy.TRANSPARENT            : transp,
-                    }).on(self._txn))
+                    }).on(txn))
 
             # Special - for unbounded recurrence we insert a value for "infinity"
             # that will allow an open-ended time-range to always match it.
@@ -963,14 +969,14 @@
                     tr.FBTYPE                      :
                         icalfbtype_to_indexfbtype["UNKNOWN"],
                     tr.TRANSPARENT                 : transp,
-                }, Return=tr.INSTANCE_ID).on(self._txn))[0][0]
+                }, Return=tr.INSTANCE_ID).on(txn))[0][0]
                 peruserdata = component.perUserTransparency(None)
                 for useruid, transp in peruserdata:
                     (yield Insert({
                         tpy.TIME_RANGE_INSTANCE_ID : instanceid,
                         tpy.USER_ID                : useruid,
                         tpy.TRANSPARENT            : transp,
-                    }).on(self._txn))
+                    }).on(txn))
 
 
     @inlineCallbacks
@@ -1007,7 +1013,35 @@
         returnValue(component)
 
 
+    @classproperty
+    def _recurrenceMaxByIDQuery(cls): #@NoSelf
+        """
+        DAL query to load RECURRANCE_MAX via an object's resource ID.
+        """
+        co = schema.CALENDAR_OBJECT
+        return Select([co.RECURRANCE_MAX], From=co,
+                      Where=co.RESOURCE_ID == Parameter("resourceID"))
+
+
     @inlineCallbacks
+    def recurrenceMax(self, txn=None):
+        """
+        Get the RECURRANCE_MAX value from the database. Occasionally we might need to do an
+        update to time-range data via a separate transaction, so we allow that to be passed in.
+    
+        @return: L{PyCalendarDateTime} result
+        """
+        # Setup appropriate txn
+        txn = txn if txn is not None else self._txn
+
+        rMax = (
+            yield self._recurrenceMaxByIDQuery.on(txn,
+                                         resourceID=self._resourceID)
+        )[0][0]
+        returnValue(parseSQLDateToPyCalendar(rMax))
+
+
+    @inlineCallbacks
     def organizer(self):
         returnValue((yield self.component()).getOrganizer())
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/test/test_sql.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/test/test_sql.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -38,6 +38,7 @@
 from txdav.common.datastore.sql_tables import schema, _BIND_MODE_DIRECT,\
     _BIND_STATUS_ACCEPTED
 from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
+from txdav.common.icommondatastore import NoSuchObjectResourceError
 
 from twistedcaldav import caldavxml
 from twistedcaldav.caldavxml import CalendarDescription
@@ -1104,3 +1105,67 @@
             supported_components.add(result)
             
         self.assertEqual(supported_components, set(("VEVENT", "VTODO",)))
+
+    @inlineCallbacks
+    def test_resourceLock(self):
+        """
+        Test CommonObjectResource.lock to make sure it locks, raises on missing resource,
+        and raises when locked and wait=False used.
+        """
+        
+        # Valid object
+        resource = yield self.calendarObjectUnderTest()
+        
+        # Valid lock
+        yield resource.lock()
+        self.assertTrue(resource._locked)
+        
+        # Setup a new transaction to verify the lock and also verify wait behavior
+        newTxn = self._sqlCalendarStore.newTransaction()
+        newResource = yield self.calendarObjectUnderTest(txn=newTxn)
+        try:
+            yield newResource.lock(wait=False)
+        except:
+            pass # OK
+        else:
+            self.fail("Expected an exception")
+        self.assertFalse(newResource._locked)
+        yield newTxn.abort()
+
+        # Commit existing transaction and verify we can get the lock using
+        yield self.commit()
+        
+        resource = yield self.calendarObjectUnderTest()
+        yield resource.lock()
+        self.assertTrue(resource._locked)
+                
+        # Setup a new transaction to verify the lock but pass in an alternative txn directly
+        newTxn = self._sqlCalendarStore.newTransaction()
+        
+        # FIXME: not sure why, but without this statement here, this portion of the test fails in a funny way.
+        # Basically the query in the try block seems to execute twice, failing each time, one of which is caught,
+        # and the other not - causing the test to fail. Seems like some state on newTxn is not being initialized?
+        yield self.calendarObjectUnderTest("2.ics", txn=newTxn)
+        
+        try:
+            yield resource.lock(wait=False, useTxn=newTxn)
+        except:
+            pass # OK
+        else:
+            self.fail("Expected an exception")
+        self.assertTrue(resource._locked)
+
+        # Test missing resource
+        resource2 = yield self.calendarObjectUnderTest("2.ics")
+        resource2._resourceID = 123456789
+        try:
+            yield resource2.lock()
+        except NoSuchObjectResourceError:
+            pass # OK
+        except:
+            self.fail("Expected a NoSuchObjectResourceError exception")
+        else:
+            self.fail("Expected an exception")
+        self.assertFalse(resource2._locked)
+
+        
\ No newline at end of file

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/util.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/caldav/datastore/util.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -44,6 +44,8 @@
 
 log = Logger()
 
+validationBypass = False
+
 def validateCalendarComponent(calendarObject, calendar, component, inserting, migrating):
     """
     Validate a calendar component for a particular calendar.
@@ -59,6 +61,9 @@
     @type component: L{VComponent}
     """
 
+    if validationBypass:
+        return
+
     if not isinstance(component, VComponent):
         raise TypeError(type(component))
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -43,6 +43,8 @@
 
 from twisted.application.service import Service
 
+from txdav.base.datastore.util import QueryCacher
+
 from twext.internet.decorate import memoizedKey
 
 from txdav.common.datastore.sql_legacy import PostgresLegacyNotificationsEmulator
@@ -136,7 +138,9 @@
                  enableCalendars=True, enableAddressBooks=True,
                  label="unlabeled", quota=(2 ** 20),
                  logLabels=False, logStats=False, logSQL=False,
-                 logTransactionWaits=0, timeoutTransactions=0):
+                 logTransactionWaits=0, timeoutTransactions=0,
+                 cacheQueries=True, cachePool="Default",
+                 cacheExpireSeconds=3600):
         assert enableCalendars or enableAddressBooks
 
         self.sqlTxnFactory = sqlTxnFactory
@@ -154,7 +158,13 @@
         self._migrating = False
         self._enableNotifications = True
 
+        if cacheQueries:
+            self.queryCacher = QueryCacher(cachePool=cachePool,
+                cacheExpireSeconds=cacheExpireSeconds)
+        else:
+            self.queryCacher = None
 
+
     def eachCalendarHome(self):
         """
         @see L{ICalendarStore.eachCalendarHome}
@@ -2018,12 +2028,24 @@
         @return: an L{CommonHomeChild} or C{None} if no such child
             exists.
         """
-        if owned:
-            query = cls._resourceIDOwnedByHomeByName
-        else:
-            query = cls._resourceIDSharedToHomeByName
-        data = yield query.on(home._txn,
-                              objectName=name, homeID=home._resourceID)
+        data = None
+        queryCacher = home._txn.store().queryCacher
+        # Only caching non-shared objects so that we don't need to invalidate
+        # in sql_legacy
+        if owned and queryCacher:
+            data = yield queryCacher.getObjectWithName(home._resourceID, name)
+
+        if data is None:
+            if owned:
+                query = cls._resourceIDOwnedByHomeByName
+            else:
+                query = cls._resourceIDSharedToHomeByName
+            data = yield query.on(home._txn,
+                                  objectName=name, homeID=home._resourceID)
+            if owned and data and queryCacher:
+                queryCacher.setObjectWithName(home._txn, home._resourceID,
+                    name, data)
+
         if not data:
             returnValue(None)
         resourceID = data[0][0]
@@ -2219,6 +2241,12 @@
         @return: a L{Deferred} which fires when the modification is complete.
         """
         oldName = self._name
+
+        queryCacher = self._home._txn.store().queryCacher
+        if queryCacher:
+            queryCacher.invalidateObjectWithName(self._home._txn,
+                self._home._resourceID, oldName)
+
         yield self._renameQuery.on(self._txn, name=name,
                                    resourceID=self._resourceID,
                                    homeID=self._home._resourceID)
@@ -2246,6 +2274,12 @@
 
     @inlineCallbacks
     def remove(self):
+
+        queryCacher = self._home._txn.store().queryCacher
+        if queryCacher:
+            queryCacher.invalidateObjectWithName(self._home._txn,
+                self._home._resourceID, self._name)
+
         yield self._deletedSyncToken()
         yield self._deleteQuery.on(self._txn, NoSuchHomeChildError,
                                    resourceID=self._resourceID)
@@ -2694,9 +2728,10 @@
         self._created = None
         self._modified = None
         self._objectText = None
+        
+        self._locked = False
 
 
-
     @classproperty
     def _allColumnsWithParent(cls): #@NoSelf
         obj = cls._objectSchema
@@ -2904,9 +2939,42 @@
     def _txn(self):
         return self._parentCollection._txn
 
+
     def transaction(self):
         return self._parentCollection._txn
 
+
+    @classmethod
+    def _selectForUpdateQuery(cls, nowait): #@NoSelf
+        """
+        DAL statement to lock a L{CommonObjectResource} by its resource ID.
+        """
+        return Select(From=cls._objectSchema, ForUpdate=True, NoWait=nowait, Where=cls._objectSchema.RESOURCE_ID == Parameter("resourceID"))
+
+
+    @inlineCallbacks
+    def lock(self, wait=True, txn=None):
+        """
+        Attempt to obtain a row lock on the object resource. 'wait' determines whether the DB call will
+        block on any existing lock held by someone else. Lock will remain until
+        transaction is complete, or fail if resource is missing, or it is already locked
+        and wait=False is used. Occasionally we need to lock via a separate transaction so we
+        pass that in too.
+
+        @param wait: whether or not to wait on someone else's lock
+        @type wait: C{bool}
+        @param txn: alternative transaction to use
+        @type txn: L{CommonStoreTransaction}
+        
+        @raise: L{NoSuchObjectResourceError} if resource does not exist, other L{Exception}
+                if already locked and NOWAIT is used.
+        """
+        
+        txn = txn if txn is not None else self._txn
+        yield self._selectForUpdateQuery(not wait).on(txn, NoSuchObjectResourceError, resourceID=self._resourceID)
+        self._locked = True
+
+
     def setComponent(self, component, inserting=False):
         raise NotImplementedError
 

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql_legacy.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/sql_legacy.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -41,7 +41,7 @@
 from twistedcaldav.sharing import Invite
 
 from txdav.common.icommondatastore import (
-    IndexedSearchException, ReservationError)
+    IndexedSearchException, ReservationError, NoSuchObjectResourceError)
 
 from twext.enterprise.dal.syntax import Update, SavepointAction
 from twext.enterprise.dal.syntax import Insert
@@ -1156,11 +1156,39 @@
         with a longer expansion.
         """
         obj = yield self.calendar.calendarObjectWithName(name)
-        yield obj.updateDatabase(
-            (yield obj.component()), expand_until=expand_until, reCreate=True
-        )
+        
+        # Use a new transaction to do this update quickly without locking the row for too long. However, the original
+        # transaction may have the row locked, so use wait=False and if that fails, fall back to using the original txn. 
+        
+        newTxn = obj.transaction().store().newTransaction()
+        try:
+            yield obj.lock(wait=False, txn=newTxn)
+        except NoSuchObjectResourceError:
+            yield newTxn.commit()
+            returnValue(None)
+        except:
+            yield newTxn.abort()
+            newTxn = None
 
+        # Now do the re-expand using the appropriate transaction
+        try:
+            if newTxn is None:
+                rmax = None
+            else:
+                rmax = (yield obj.recurrenceMax(txn=newTxn))
 
+            if rmax is None or rmax < expand_until:
+                yield obj.updateDatabase(
+                    (yield obj.component()),
+                    expand_until=expand_until,
+                    reCreate=True,
+                    txn=newTxn,
+                )
+        finally:
+            if newTxn is not None:
+                yield newTxn.commit()
+
+
     @inlineCallbacks
     def testAndUpdateIndex(self, minDate):
         # Find out if the index is expanded far enough

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/test/util.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/common/datastore/test/util.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -331,7 +331,7 @@
 
 
 @inlineCallbacks
-def populateCalendarsFrom(requirements, store):
+def populateCalendarsFrom(requirements, store, migrating=False):
     """
     Populate C{store} from C{requirements}.
 
@@ -341,6 +341,8 @@
     @param store: the L{IDataStore} to populate with calendar data.
     """
     populateTxn = store.newTransaction()
+    if migrating:
+        populateTxn._migrating = True
     for homeUID in requirements:
         calendars = requirements[homeUID]
         if calendars is not None:

Modified: CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/xml/element.py
===================================================================
--- CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/xml/element.py	2012-04-13 18:59:38 UTC (rev 9106)
+++ CalendarServer/branches/users/gaya/ldapdirectorybacker/txdav/xml/element.py	2012-04-13 20:13:22 UTC (rev 9107)
@@ -82,8 +82,18 @@
     """
     Add an XML element class to this module's namespace.
     """
-    globals()[elementClass.__name__] = elementClass
-    __all__.append(elementClass.__name__)
+    env = globals()
+    name = elementClass.__name__
+
+    if name in env:
+        raise AssertionError(
+            "Attempting to register element class %s multiple times: (%r, %r)"
+            % (name, env[name], elementClass)
+        )
+
+    env[name] = elementClass
+    __all__.append(name)
+
     return elementClass
 
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120413/358bb957/attachment-0001.html>


More information about the calendarserver-changes mailing list