[CalendarServer-changes] [7697] CalendarServer/branches/users/cdaboo/timezones
source_changes at macosforge.org
source_changes at macosforge.org
Thu Jun 30 13:44:14 PDT 2011
Revision: 7697
http://trac.macosforge.org/projects/calendarserver/changeset/7697
Author: cdaboo at apple.com
Date: 2011-06-30 13:44:13 -0700 (Thu, 30 Jun 2011)
Log Message:
-----------
Merge from trunk.
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/timezones/benchmark
CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/root.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/test/test_root.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/changeip_calendar.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/export.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/notifications.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/principals.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/purge.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/resources.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_changeip.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_gateway.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_purge_old_events.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_resources.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/util.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/warmup.py
CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-apple.plist
CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-test.plist
CalendarServer/branches/users/cdaboo/timezones/contrib/certupdate/calendarcertupdate.py
CalendarServer/branches/users/cdaboo/timezones/contrib/migration/calendarmigrator.py
CalendarServer/branches/users/cdaboo/timezones/contrib/migration/test/test_migrator.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/benchmark.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/config.plist
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/ical.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/logger.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/population.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/profiles.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/request-data/sl_post_availability.request
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/sim.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_ical.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_profiles.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_sim.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/run.sh
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/setbackend.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/stats.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/test_stats.py
CalendarServer/branches/users/cdaboo/timezones/doc/calendarserver_monitor_notifications.8
CalendarServer/branches/users/cdaboo/timezones/pyflakes
CalendarServer/branches/users/cdaboo/timezones/run
CalendarServer/branches/users/cdaboo/timezones/setup.py
CalendarServer/branches/users/cdaboo/timezones/support/build.sh
CalendarServer/branches/users/cdaboo/timezones/support/py.sh
CalendarServer/branches/users/cdaboo/timezones/test
CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/syntax.py
CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/test/test_sqlsyntax.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/caldavxml.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/datafilters/test/test_peruserdata.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/dateops.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/appleopendirectory.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/augment.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/directory.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/ldapdirectory.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/principal.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test-default.xml
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test.xml
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_aggregate.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_augment.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_livedirectory.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_opendirectory.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_principal.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipaldb.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipalmembers.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_xmlfile.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/wiki.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlaugmentsparser.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlfile.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directorybackedaddressbook.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/ical.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/instance.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/localization.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/mail.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/get.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_addressbook_common.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_common.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_calendar_query.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_multiget_common.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/notify.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookquery.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookqueryfilter.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarquery.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarqueryfilter.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/expression.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/sqlgenerator.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/resource.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/schedule.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/icaldiff.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/implicit.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/scheduler.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/sharing.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/storebridge.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184A66-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184D26-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185326-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31854DA-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31856AC-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318585A-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185A14-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185BBD-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185D63-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185F20-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31860C8-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318627C-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186426-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31865E4-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186792-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186938-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186ADE-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186C96-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186E3A-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186FE7-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318719A-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3187343-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188906-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188B3A-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188CFF-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188EAA-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189058-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189203-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31893C2-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189572-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189716-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31898D4-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189A88-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189C32-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189DEC-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189F94-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A148-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A2F3-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A6E1-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A898-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AA54-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ABFE-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ADAA-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AF53-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B108-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B2D2-1ED0-11D9-A5E0-000A958A3252.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_calendarquery.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_icalendar.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_mail.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_multiget.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_resource.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_schedule.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_sharing.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_timezonestdservice.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_validation.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_wrapping.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_xml.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/util.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/upgrade.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/vcard.py
CalendarServer/branches/users/cdaboo/timezones/txdav/base/datastore/subpostgres.py
CalendarServer/branches/users/cdaboo/timezones/txdav/base/propertystore/test/test_sql.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/scheduling.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/common.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_scheduling.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_sql.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/util.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/icalendarstore.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/common.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_sql.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/util.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/iaddressbookstore.py
CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql.py
CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql_legacy.py
CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/test_util.py
CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/util.py
Added Paths:
-----------
CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_calverify
CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_dbinspect
CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_shell
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/calverify.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/cmdline.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/dbinspect.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/shell.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/tables.py
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_export.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.csv
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_trafficlogger.py
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/trafficlogger.py
CalendarServer/branches/users/cdaboo/timezones/contrib/tools/backup
CalendarServer/branches/users/cdaboo/timezones/contrib/tools/fakecalendardata.py
CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/
CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch
CalendarServer/branches/users/cdaboo/timezones/sim
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/test/test_expression.py
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/AnotherEvent.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/OneEvent.ics
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/ThirdEvent.ics
Removed Paths:
-------------
CalendarServer/branches/users/cdaboo/timezones/bin/carddavd
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.txt
CalendarServer/branches/users/cdaboo/timezones/contrib/performance/todo
CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch
Property Changed:
----------------
CalendarServer/branches/users/cdaboo/timezones/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/platform/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/platform/darwin/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/platform/darwin/od/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/webadmin/
CalendarServer/branches/users/cdaboo/timezones/calendarserver/webcal/
CalendarServer/branches/users/cdaboo/timezones/support/
CalendarServer/branches/users/cdaboo/timezones/support/build.sh
CalendarServer/branches/users/cdaboo/timezones/twext/
CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/
CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/
CalendarServer/branches/users/cdaboo/timezones/twext/internet/
CalendarServer/branches/users/cdaboo/timezones/twext/protocols/
CalendarServer/branches/users/cdaboo/timezones/twext/python/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/auth/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/channel/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/client/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/dav/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/dav/element/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/dav/method/
CalendarServer/branches/users/cdaboo/timezones/twext/web2/filter/
CalendarServer/branches/users/cdaboo/timezones/twisted/plugins/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/client/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/datafilters/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/
CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/
CalendarServer/branches/users/cdaboo/timezones/txdav/
CalendarServer/branches/users/cdaboo/timezones/txdav/base/
CalendarServer/branches/users/cdaboo/timezones/txdav/base/datastore/
CalendarServer/branches/users/cdaboo/timezones/txdav/base/propertystore/
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_index_file.py
CalendarServer/branches/users/cdaboo/timezones/txdav/common/
CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/
Property changes on: CalendarServer/branches/users/cdaboo/timezones
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
+ /CalendarServer/branches/config-separation:4379-4443
/CalendarServer/branches/egg-info-351:4589-4625
/CalendarServer/branches/generic-sqlstore:6167-6191
/CalendarServer/branches/new-store:5594-5934
/CalendarServer/branches/new-store-no-caldavfile:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464:4465-4957
/CalendarServer/branches/users/cdaboo/pods:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar:7085-7206
/CalendarServer/branches/users/cdaboo/pycard:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187:5188-5440
/CalendarServer/branches/users/glyph/conn-limit:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge:4971-5080
/CalendarServer/branches/users/glyph/dalify:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect:6824-6876
/CalendarServer/branches/users/glyph/deploybuild:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres:6592-6614
/CalendarServer/branches/users/glyph/linux-tests:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7:6369-6445
/CalendarServer/branches/users/glyph/new-export:7444-7485
/CalendarServer/branches/users/glyph/oracle:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls:7340-7351
/CalendarServer/branches/users/glyph/quota:7604-7637
/CalendarServer/branches/users/glyph/sendfdport:5388-5424
/CalendarServer/branches/users/glyph/sharedpool:6490-6550
/CalendarServer/branches/users/glyph/sql-store:5929-6073
/CalendarServer/branches/users/glyph/subtransactions:7248-7258
/CalendarServer/branches/users/glyph/uidexport:7673-7676
/CalendarServer/branches/users/glyph/use-system-twisted:5084-5149
/CalendarServer/branches/users/sagen/inboxitems:7380-7381
/CalendarServer/branches/users/sagen/locations-resources:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066:4068-4075
/CalendarServer/branches/users/sagen/resources-2:5084-5093
/CalendarServer/branches/users/wsanchez/transations:5515-5593
/CalendarServer/trunk:7443-7695
Modified: CalendarServer/branches/users/cdaboo/timezones/benchmark
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/benchmark 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/benchmark 2011-06-30 20:44:13 UTC (rev 7697)
@@ -21,6 +21,6 @@
wd="$(cd "$(dirname "$0")" && pwd -L)";
-export PYTHONPATH="$("${wd}/run" -p):${wd}/../CalDAVClientLibrary/src";
+export PYTHONPATH="$("${wd}/run" -p)";
exec "${wd}/contrib/performance/benchmark" "$@";
Copied: CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_calverify (from rev 7695, CalendarServer/trunk/bin/calendarserver_calverify)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_calverify (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_calverify 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2011 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+ if "PYTHONPATH" in globals():
+ sys.path.insert(0, PYTHONPATH)
+ else:
+ from os.path import dirname, abspath, join
+ from subprocess import Popen, PIPE
+
+ home = dirname(dirname(abspath(__file__)))
+ run = join(home, "run")
+
+ child = Popen((run, "-p"), stdout=PIPE)
+ path, stderr = child.communicate()
+
+ path = path.rstrip("\n")
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ sys.argv[1:1] = ["-f", join(home, "conf", "caldavd-dev.plist")]
+
+ from calendarserver.tools.calverify import main
+ main()
Copied: CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_dbinspect (from rev 7695, CalendarServer/trunk/bin/calendarserver_dbinspect)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_dbinspect (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_dbinspect 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2011 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+ if "PYTHONPATH" in globals():
+ sys.path.insert(0, PYTHONPATH)
+ else:
+ from os.path import dirname, abspath, join
+ from subprocess import Popen, PIPE
+
+ home = dirname(dirname(abspath(__file__)))
+ run = join(home, "run")
+
+ child = Popen((run, "-p"), stdout=PIPE)
+ path, stderr = child.communicate()
+
+ path = path.rstrip("\n")
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ sys.argv[1:1] = ["-f", join(home, "conf", "caldavd-dev.plist")]
+
+ from calendarserver.tools.dbinspect import main
+ main()
Copied: CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_shell (from rev 7695, CalendarServer/trunk/bin/calendarserver_shell)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_shell (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/bin/calendarserver_shell 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+##
+# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+import sys
+
+#PYTHONPATH
+
+if __name__ == "__main__":
+ if "PYTHONPATH" in globals():
+ sys.path.insert(0, PYTHONPATH)
+ else:
+ from os.path import dirname, abspath, join
+ from subprocess import Popen, PIPE
+
+ home = dirname(dirname(abspath(__file__)))
+ run = join(home, "run")
+
+ child = Popen((run, "-p"), stdout=PIPE)
+ path, stderr = child.communicate()
+
+ path = path.rstrip("\n")
+
+ if child.wait() == 0:
+ sys.path[0:0] = path.split(":")
+
+ sys.argv[1:1] = ["-f", join(home, "conf", "caldavd-dev.plist")]
+
+ from calendarserver.tools.shell import main
+ main()
Deleted: CalendarServer/branches/users/cdaboo/timezones/bin/carddavd
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/bin/carddavd 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/bin/carddavd 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,125 +0,0 @@
-#!/bin/bash
-# -*- sh-basic-offset: 2 -*-
-
-##
-# 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.
-##
-
-#PATH
-#PYTHONPATH
-
-daemonize="";
-errorlogenabled="";
-username="";
-groupname="";
-configfile="";
-twistdpath="$(type -p twistd)";
-plugin_name="carddav";
-service_type="";
-profile="";
-twistd_reactor="";
-child_reactor=""
-
-py_version ()
-{
- local python="$1"; shift
- echo "$("${python}" -c "from distutils.sysconfig import get_python_version; print get_python_version()")";
-}
-
-try_python ()
-{
- local python="$1"; shift
-
- if [ -z "${python}" ]; then return 1; fi;
-
- if ! type "${python}" > /dev/null 2>&1; then return 1; fi;
- local py_version="$(py_version "${python}")";
- if [ "${py_version/./}" -lt "24" ]; then return 1; fi;
-
- return 0;
-}
-
-for v in "" "2.6" "2.5"; do
- for p in \
- "${PYTHON:=}" \
- "python${v}" \
- "/usr/local/bin/python${v}" \
- "/usr/local/python/bin/python${v}" \
- "/usr/local/python${v}/bin/python${v}" \
- "/opt/bin/python${v}" \
- "/opt/python/bin/python${v}" \
- "/opt/python${v}/bin/python${v}" \
- "/Library/Frameworks/Python.framework/Versions/${v}/bin/python" \
- "/opt/local/bin/python${v}" \
- "/sw/bin/python${v}" \
- ;
- do
- if try_python "${p}"; then python="${p}"; break; fi;
- done;
- if [ -n "${python:-}" ]; then break; fi;
-done;
-
-if [ -z "${python:-}" ]; then
- echo "No suitable python found.";
- exit 1;
-fi;
-
-usage ()
-{
- program="$(basename "$0")";
-
- if [ "${1--}" != "-" ]; then echo "$@"; echo; fi;
-
- echo "Usage: ${program} [-hX] [-u username] [-g groupname] [-T twistd] [-t type] [-f carddavd.plist] [-p statsfile]";
- echo "Options:";
- echo " -h Print this help and exit";
- echo " -X Do not daemonize";
- echo " -u User name to run as";
- echo " -g Group name to run as";
- echo " -f Configuration file to read";
- echo " -T Path to twistd binary";
- echo " -t Process type (Master, Slave or Combined)";
- echo " -p Path to the desired pstats file.";
- echo " -R The Twisted Reactor to run [${reactor}]";
-
- if [ "${1-}" == "-" ]; then return 0; fi;
- exit 64;
-}
-
-while getopts 'hXLu:g:f:T:P:t:p:R:' option; do
- case "${option}" in
- '?') usage; ;;
- 'h') usage -; exit 0; ;;
- 'X') daemonize="-n"; ;;
- 'L') errorlogenabled="-o ErrorLogEnabled=False"; ;;
- 'f') configfile="-f ${OPTARG}"; ;;
- 'T') twistdpath="${OPTARG}"; ;;
- 'u') username="-u ${OPTARG}"; ;;
- 'g') groupname="-g ${OPTARG}"; ;;
- 'P') plugin_name="${OPTARG}"; ;;
- 't') service_type="-o ProcessType=${OPTARG}"; ;;
- 'p') profile="-o Profiling/Enabled=True -o Profiling/BaseDirectory=${OPTARG}"; ;;
- 'R') twistd_reactor="--reactor=${OPTARG}"; child_reactor="-o Twisted/reactor=${OPTARG}"; ;;
- esac;
-done;
-
-shift $((${OPTIND} - 1));
-
-if [ $# != 0 ]; then usage "Unrecognized arguments:" "$@"; fi;
-
-export PYTHONPATH
-
-
-exec "${python}" "${twistdpath}" ${twistd_reactor} ${daemonize} ${username} ${groupname} "${plugin_name}" ${configfile} ${service_type} ${errorlogenabled} ${profile} ${child_reactor};
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver
___________________________________________________________________
Modified: svn:ignore
- version.py
+ version.py
*.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/platform
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/platform/darwin
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/platform/darwin/od
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/root.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/root.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -323,10 +323,13 @@
if segments[0] in ("inbox", "timezones"):
request.checkedSACL = True
- elif (len(segments) > 2 and (segments[1] == "wikis" or
- (segments[1] == "__uids__" and segments[2].startswith("wiki-")))):
-
- # This is a wiki-related resource. SACLs are not checked.
+ elif (len(segments) > 2 and segments[0] == "calendars" and
+ (
+ segments[1] == "wikis" or
+ (segments[1] == "__uids__" and segments[2].startswith("wiki-"))
+ )
+ ):
+ # This is a wiki-related calendar resource. SACLs are not checked.
request.checkedSACL = True
# The authzuser value is set to that of the wiki principal if
@@ -368,7 +371,7 @@
request.extendedLogItems = {}
request.extendedLogItems["xff"] = remote_ip[0]
- if request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1:
+ if config.EnableResponseCache and request.method == "PROPFIND" and not getattr(request, "notInCache", False) and len(segments) > 1:
try:
authnUser, authzUser = (yield self.authenticate(request))
request.authnUser = authnUser
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/test/test_root.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/test/test_root.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/provision/test/test_root.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -61,10 +61,13 @@
RootResource.CheckSACL = FakeCheckSACL(sacls={"calendar": ["dreid"]})
- augment.AugmentService = augment.AugmentXMLDB(
- xmlFiles=(augmentsFile.path,)
+ directory = XMLDirectoryService(
+ {
+ "xmlFile" : xmlFile,
+ "augmentService" :
+ augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
+ }
)
- directory = XMLDirectoryService({"xmlFile" : xmlFile})
principals = DirectoryPrincipalProvisioningResource(
"/principals/",
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tap/util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -45,7 +45,7 @@
from twisted.python.reflect import namedClass
from twistedcaldav.bind import doBind
-from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory import calendaruserproxy
from twistedcaldav.directory.addressbook import DirectoryAddressBookHomeProvisioningResource
from twistedcaldav.directory.aggregate import AggregateDirectoryService
from twistedcaldav.directory.calendar import DirectoryCalendarHomeProvisioningResource
@@ -191,11 +191,15 @@
if txnFactory is not None:
return CommonSQLDataStore(
txnFactory, notifierFactory, FilePath(config.AttachmentsRoot),
- config.EnableCalDAV, config.EnableCardDAV
+ config.EnableCalDAV, config.EnableCardDAV,
+ quota=config.UserQuota
)
else:
- return CommonFileDataStore(FilePath(config.DocumentRoot),
- notifierFactory, config.EnableCalDAV, config.EnableCardDAV)
+ return CommonFileDataStore(
+ FilePath(config.DocumentRoot),
+ notifierFactory, config.EnableCalDAV, config.EnableCardDAV,
+ quota=config.UserQuota
+ )
@@ -212,7 +216,7 @@
log.info("Configuring augment service of type: %s" % (augmentClass,))
try:
- augment.AugmentService = augmentClass(**config.AugmentService.params)
+ augmentService = augmentClass(**config.AugmentService.params)
except IOError:
log.error("Could not start augment service")
raise
@@ -228,6 +232,7 @@
log.info("Configuring directory service of type: %s"
% (config.DirectoryService.type,))
+ config.DirectoryService.params.augmentService = augmentService
baseDirectory = directoryClass(config.DirectoryService.params)
# Wait for the directory to become available
@@ -244,6 +249,7 @@
log.info("Configuring resource service of type: %s" % (resourceClass,))
+ config.ResourceService.params.augmentService = augmentService
resourceDirectory = resourceClass(config.ResourceService.params)
resourceDirectory.realmName = baseDirectory.realmName
directories.append(resourceDirectory)
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Copied: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/calverify.py (from rev 7695, CalendarServer/trunk/calendarserver/tools/calverify.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/calverify.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/calverify.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,641 @@
+#!/usr/bin/env python
+# -*- test-case-name: calendarserver.tools.test.test_calverify -*-
+##
+# Copyright (c) 2011 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.
+##
+
+"""
+This tool scans the calendar store to analyze organizer/attendee event
+states to verify that the organizer's view of attendee state matches up
+with the attendees' views. It can optionally apply a fix to bring the two
+views back into line.
+
+In theory the implicit scheduling model should eliminate the possibility
+of mismatches, however, because we store separate resources for organizer
+and attendee events, there is a possibility of mismatch. This is greatly
+lessened via the new transaction model of database changes, but it is
+possible there are edge cases or actual implicit processing errors we have
+missed. This tool will allow us to track mismatches to help determine these
+errors and get them fixed.
+
+Even in the long term if we move to a "single instance" store where the
+organizer event resource is the only one we store (with attendee views
+derived from that), in a situation where we have server-to-server scheduling
+it is possible for mismatches to creep in. In that case having a way to analyze
+multiple DBs for inconsistency would be good too.
+
+"""
+
+from calendarserver.tap.util import directoryFromConfig
+from calendarserver.tools import tables
+from calendarserver.tools.cmdline import utilityMain
+from pycalendar import definitions
+from pycalendar.calendar import PyCalendar
+from pycalendar.datetime import PyCalendarDateTime
+from pycalendar.period import PyCalendarPeriod
+from twext.enterprise.dal.syntax import Select, Parameter, Count
+from twisted.application.service import Service
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.python import log
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options
+from twistedcaldav.dateops import pyCalendarTodatetime
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
+import collections
+import os
+import sys
+import time
+
+def usage(e=None):
+ if e:
+ print e
+ print ""
+ try:
+ CalVerifyOptions().opt_help()
+ except SystemExit:
+ pass
+ if e:
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
+
+description = '\n'.join(
+ wordWrap(
+ """
+ Usage: calendarserver_calverify [options] [input specifiers]\n
+ """,
+ int(os.environ.get('COLUMNS', '80'))
+ )
+)
+
+class CalVerifyOptions(Options):
+ """
+ Command-line options for 'calendarserver_calverify'
+ """
+
+ synopsis = description
+
+ optFlags = [
+ ['fix', 'x', "Fix mismatches."],
+ ['missing', 'v', "Show 'orphaned' homes."],
+ ['verbose', 'v', "Verbose logging."],
+ ]
+
+ optParameters = [
+ ['config', 'f', DEFAULT_CONFIG_FILE, "Specify caldavd.plist configuration path."],
+ ['data', 'd', "./calverify-data", "Path where ancillary data is stored."],
+ ]
+
+ def __init__(self):
+ super(CalVerifyOptions, self).__init__()
+ self.outputName = '-'
+
+ def opt_output(self, filename):
+ """
+ Specify output file path (default: '-', meaning stdout).
+ """
+ self.outputName = filename
+
+ opt_o = opt_output
+
+ def openOutput(self):
+ """
+ Open the appropriate output file based on the '--output' option.
+ """
+ if self.outputName == '-':
+ return sys.stdout
+ else:
+ return open(self.outputName, 'wb')
+
+
+
+class CalVerifyService(Service, object):
+ """
+ Service which runs, exports the appropriate records, then stops the reactor.
+ """
+
+ def __init__(self, store, options, output, reactor, config):
+ super(CalVerifyService, self).__init__()
+ self.store = store
+ self.options = options
+ self.output = output
+ self.reactor = reactor
+ self.config = config
+ self._directory = None
+
+
+ def startService(self):
+ """
+ Start the service.
+ """
+ super(CalVerifyService, self).startService()
+ self.doCalVerify()
+
+
+ @inlineCallbacks
+ def doCalVerify(self):
+ """
+ Do the export, stopping the reactor when done.
+ """
+ self.txn = self.store.newTransaction()
+ try:
+ if self.options["missing"]:
+ yield self.doOrphans()
+ yield self.doScan(self.options["fix"])
+ yield self.txn.commit()
+ self.txn = None
+
+ self.output.close()
+ except:
+ log.err()
+
+ self.reactor.stop()
+
+
+ @inlineCallbacks
+ def doOrphans(self):
+ """
+ Report on home collections for which there are no directory records.
+ """
+ print "\n---- Finding calendar homes with no directory record ----"
+
+ if self.options["verbose"]:
+ t = time.time()
+ uids = yield self.getAllHomeUIDs()
+ if self.options["verbose"]:
+ print "getAllHomeUIDs time: %.1fs" % (time.time() - t,)
+ missing = []
+ uids_len = len(uids)
+ uids_div = 1 if uids_len < 100 else uids_len / 100
+
+ for ctr, uid in enumerate(uids):
+ if self.options["verbose"] and divmod(ctr, uids_div)[1] == 0:
+ print "%d of %d (%d%%)" % (
+ ctr+1,
+ uids_len,
+ ((ctr+1) * 100 / uids_len),
+ )
+
+ if self.directoryService().recordWithGUID(uid[0]) is None:
+ contents = yield self.countHomeContents(uid)
+ missing.append((uid[0], contents,))
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("Owner UID", "Calendar Objects"))
+ for uid, count in missing:
+ table.addRow((
+ uid,
+ count,
+ ))
+
+ self.output.write("\n")
+ self.output.write("Homes without a matching directory record (total=%d):\n" % (len(missing),))
+ table.printTable(os=self.output)
+
+
+ @inlineCallbacks
+ def getAllHomeUIDs(self):
+ ch = schema.CALENDAR_HOME
+ rows = (yield Select(
+ [ch.OWNER_UID,],
+ From=ch,
+ ).on(self.txn))
+ returnValue(tuple(rows))
+
+ @inlineCallbacks
+ def countHomeContents(self, uid):
+ ch = schema.CALENDAR_HOME
+ cb = schema.CALENDAR_BIND
+ co = schema.CALENDAR_OBJECT
+ kwds = { "UID" : uid }
+ rows = (yield Select(
+ [Count(co.RESOURCE_ID),],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN)).join(
+ co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID)),
+ Where=(ch.OWNER_UID == Parameter("UID"))
+ ).on(self.txn, **kwds))
+ returnValue(int(rows[0][0]) if rows else 0)
+
+ @inlineCallbacks
+ def doScan(self, fix):
+
+ print "\n---- Scanning calendar data ----"
+
+ self.start = PyCalendarDateTime.getToday()
+ self.start.setDateOnly(False)
+ self.end = self.start.duplicate()
+ self.end.offsetYear(1)
+ self.fix = fix
+
+ if self.options["verbose"]:
+ t = time.time()
+ rows = yield self.getAllResourceInfo(self.start)
+ if self.options["verbose"]:
+ print "getAllResourceInfo time: %.1fs" % (time.time() - t,)
+ print "Number of events to process: %s" % (len(rows,))
+
+ # Split into organizer events and attendee events
+ self.organized = []
+ self.organized_byuid = {}
+ self.attended = []
+ self.attended_byuid = collections.defaultdict(list)
+ self.matched_attendee_to_organizer = collections.defaultdict(set)
+ for owner, resid, uid, md5, organizer in rows:
+ if organizer.startswith("urn:uuid:") and owner == organizer[9:]:
+ self.organized.append((owner, resid, uid, md5, organizer,))
+ self.organized_byuid[uid] = (owner, resid, uid, md5, organizer,)
+ else:
+ self.attended.append((owner, resid, uid, md5, organizer,))
+ self.attended_byuid[uid].append((owner, resid, uid, md5, organizer,))
+
+ print "Number of organizer events to process: %s" % (len(self.organized),)
+ print "Number of attendee events to process: %s" % (len(self.attended,))
+
+ yield self.verifyAllAttendeesForOrganizer()
+ yield self.verifyAllOrganizersForAttendee()
+
+ yield succeed(None)
+
+ @inlineCallbacks
+ def getAllResourceInfo(self, start):
+ co = schema.CALENDAR_OBJECT
+ cb = schema.CALENDAR_BIND
+ ch = schema.CALENDAR_HOME
+ tr = schema.TIME_RANGE
+ kwds = { "Start" : pyCalendarTodatetime(start) }
+ rows = (yield Select(
+ [ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER,],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID)).join(
+ co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN).And(
+ cb.CALENDAR_RESOURCE_NAME != "inbox").And(
+ co.ORGANIZER != "")).join(
+ tr, type="left", on=(co.RESOURCE_ID == tr.CALENDAR_OBJECT_RESOURCE_ID)),
+ Where=(tr.START_DATE >= Parameter("Start")).Or(co.RECURRANCE_MAX == "1900-01-01"),
+ GroupBy=(ch.OWNER_UID, co.RESOURCE_ID, co.ICALENDAR_UID, co.MD5, co.ORGANIZER,),
+ ).on(self.txn, **kwds))
+ returnValue(tuple(rows))
+
+ @inlineCallbacks
+ def verifyAllAttendeesForOrganizer(self):
+ """
+ Make sure that for each organizer, each referenced attendee has a consistent view of the organizer's event.
+ We will look for events that an organizer has and are missing for the attendee, and events that an organizer's
+ view of attendee status does not match the attendee's view of their own status.
+ """
+
+ print "\n---- Verifying Organizer events against Attendee copies ----"
+
+ results_missing = []
+ results_mismatch = []
+ organized_len = len(self.organized)
+ organizer_div = 1 if organized_len < 100 else organized_len / 100
+
+ # Test organized events
+ 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" % (
+ ctr+1,
+ organized_len,
+ ((ctr+1) * 100 / organized_len),
+ len(results_missing),
+ len(results_mismatch),
+ )
+
+ # Get the organizer's view of attendee states
+ organizer, resid, uid, _ignore_md5, _ignore_organizer = organizerEvent
+ calendar = yield self.getCalendar(resid)
+ organizerViewOfAttendees = self.buildAttendeeStates(calendar, self.start, self.end)
+ try:
+ del organizerViewOfAttendees[organizer]
+ except KeyError:
+ # Odd - the organizer is not an attendee - this usually does not happen
+ pass
+ if len(organizerViewOfAttendees) == 0:
+ continue
+
+ # Get attendee states for matching UID
+ eachAttendeesOwnStatus = {}
+ for attendeeEvent in self.attended_byuid.get(uid, ()):
+ owner, resid, uid, _ignore_md5, _ignore_organizer = attendeeEvent
+ calendar = yield self.getCalendar(resid)
+ eachAttendeesOwnStatus[owner] = self.buildAttendeeStates(calendar, self.start, self.end, attendee_only=owner)
+
+ # Look at each attendee in the organizer's meeting
+ for organizerAttendee, organizerViewOfStatus in organizerViewOfAttendees.iteritems():
+ broken = False
+
+ self.matched_attendee_to_organizer[uid].add(organizerAttendee)
+
+ # If an entry for the attendee exists, then check whether attendee status matches
+ if organizerAttendee in eachAttendeesOwnStatus:
+ attendeeOwnStatus = eachAttendeesOwnStatus[organizerAttendee].get(organizerAttendee, set())
+
+ if organizerViewOfStatus != attendeeOwnStatus:
+ # Check that the difference is only cancelled or declined on the organizers side
+ for _ignore_organizerInstance, partstat in organizerViewOfStatus.difference(attendeeOwnStatus):
+ if partstat not in ("DECLINED", "CANCELLED"):
+ results_mismatch.append((uid, organizer, organizerAttendee))
+ broken = True
+ break
+ # Check that the difference is only cancelled on the attendees side
+ for _ignore_attendeeInstance, partstat in attendeeOwnStatus.difference(organizerViewOfStatus):
+ if partstat not in ("CANCELLED",):
+ if not broken:
+ results_mismatch.append((uid, organizer, organizerAttendee))
+ broken = True
+ break
+
+ # Check that the status for this attendee is always declined which means a missing copy of the event is OK
+ else:
+ for _ignore_instance_id, partstat in organizerViewOfStatus:
+ if partstat not in ("DECLINED", "CANCELLED"):
+ results_missing.append((uid, organizer, organizerAttendee,))
+ broken = True
+ break
+
+ # If there was a problem we can fix it
+ if broken and self.fix:
+ # TODO: This is where we attempt a fix
+ #self.fixEvent(organizer, organizerAttendee, eventpath, attendeePaths.get(organizerAttendee, None))
+ pass
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("Organizer", "Attendee", "Event UID",))
+ for item in results_missing:
+ uid, organizer, attendee = item
+ organizer_record = self.directoryService().recordWithGUID(organizer)
+ attendee_record = self.directoryService().recordWithGUID(attendee)
+ table.addRow((
+ "%s/%s (%s)" % (organizer_record.recordType if organizer_record else "-", organizer_record.shortNames[0] if organizer_record else "-", organizer,),
+ "%s/%s (%s)" % (attendee_record.recordType if attendee_record else "-", attendee_record.shortNames[0] if attendee_record else "-", attendee,),
+ uid,
+ ))
+
+ self.output.write("\n")
+ self.output.write("Events missing from Attendee's calendars (total=%d):\n" % (len(results_missing),))
+ table.printTable(os=self.output)
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("Organizer", "Attendee", "Event UID",))
+ for item in results_mismatch:
+ uid, organizer, attendee = item
+ organizer_record = self.directoryService().recordWithGUID(organizer)
+ attendee_record = self.directoryService().recordWithGUID(attendee)
+ table.addRow((
+ "%s/%s (%s)" % (organizer_record.recordType if organizer_record else "-", organizer_record.shortNames[0] if organizer_record else "-", organizer,),
+ "%s/%s (%s)" % (attendee_record.recordType if attendee_record else "-", attendee_record.shortNames[0] if attendee_record else "-", attendee,),
+ uid,
+ ))
+
+ self.output.write("\n")
+ self.output.write("Events mismatched between Organizer's and Attendee's calendars (total=%d):\n" % (len(results_mismatch),))
+ table.printTable(os=self.output)
+
+ @inlineCallbacks
+ def verifyAllOrganizersForAttendee(self):
+ """
+ Make sure that for each attendee, there is a matching event for the organizer.
+ """
+
+ print "\n---- Verifying Attendee events against Organizer copies ----"
+
+ # Now try to match up each attendee event
+ missing = []
+ mismatched = []
+ attended_len = len(self.attended)
+ attended_div = 1 if attended_len < 100 else attended_len / 100
+
+ 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" % (
+ ctr+1,
+ attended_len,
+ ((ctr+1) * 100 / attended_len),
+ len(missing),
+ len(mismatched),
+ )
+
+ attendee, resid, uid, _ignore_md5, organizer = attendeeEvent
+ calendar = yield self.getCalendar(resid)
+ eachAttendeesOwnStatus = self.buildAttendeeStates(calendar, self.start, self.end, attendee_only=attendee)
+ if attendee not in eachAttendeesOwnStatus:
+ continue
+
+ # Only care about data for hosted organizers
+ if not organizer.startswith("urn:uuid:"):
+ continue
+ organizer = organizer[9:]
+
+ if uid not in self.organized_byuid:
+
+ # Check whether attendee has all instances cancelled
+ if self.allCancelled(eachAttendeesOwnStatus):
+ continue
+
+ missing.append((uid, attendee, organizer,))
+
+ # If there is a miss we fix by removing the attendee data
+ if self.fix:
+ # TODO: This is where we attempt a fix
+ pass
+
+ elif attendee not in self.matched_attendee_to_organizer[uid]:
+ # Check whether attendee has all instances cancelled
+ if self.allCancelled(eachAttendeesOwnStatus):
+ continue
+
+ mismatched.append((uid, attendee, organizer,))
+
+ # If there is a mismatch we fix by re-inviting the attendee
+ if self.fix:
+ # TODO: This is where we attempt a fix
+ pass
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("UID", "Owner", "Organizer",))
+ missing.sort()
+ unique_set = set()
+ for item in missing:
+ uid, attendee, organizer = item
+ unique_set.add(uid)
+ if organizer:
+ organizerRecord = self.directoryService().recordWithGUID(organizer)
+ organizer = "%s/%s (%s)" % (organizerRecord.recordType if organizerRecord else "-", organizerRecord.shortNames[0] if organizerRecord else "-", organizer,)
+ attendeeRecord = self.directoryService().recordWithGUID(attendee)
+ table.addRow((
+ uid,
+ "%s/%s (%s)" % (attendeeRecord.recordType if attendeeRecord else "-", attendeeRecord.shortNames[0] if attendeeRecord else "-", attendee,),
+ organizer,
+ ))
+
+ self.output.write("\n")
+ self.output.write("Attendee events missing in Organizer's calendar (total=%d, unique=%d):\n" % (len(missing), len(unique_set),))
+ table.printTable(os=self.output)
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("UID", "Owner", "Organizer",))
+ mismatched.sort()
+ for item in mismatched:
+ uid, attendee, organizer = item
+ if organizer:
+ organizerRecord = self.directoryService().recordWithGUID(organizer)
+ organizer = "%s/%s (%s)" % (organizerRecord.recordType if organizerRecord else "-", organizerRecord.shortNames[0] if organizerRecord else "-", organizer,)
+ attendeeRecord = self.directoryService().recordWithGUID(attendee)
+ table.addRow((
+ uid,
+ "%s/%s (%s)" % (attendeeRecord.recordType if attendeeRecord else "-", attendeeRecord.shortNames[0] if attendeeRecord else "-", attendee,),
+ organizer,
+ ))
+
+ self.output.write("\n")
+ self.output.write("Attendee events mismatched in Organizer's calendar (total=%d):\n" % (len(mismatched),))
+ table.printTable(os=self.output)
+
+
+ @inlineCallbacks
+ def getCalendar(self, resid):
+ co = schema.CALENDAR_OBJECT
+ kwds = { "ResourceID" : resid }
+ rows = (yield Select(
+ [co.ICALENDAR_TEXT],
+ From=co,
+ Where=(
+ co.RESOURCE_ID == Parameter("ResourceID")
+ ),
+ ).on(self.txn, **kwds))
+ returnValue(PyCalendar.parseText(rows[0][0]) if rows else None)
+
+ def buildAttendeeStates(self, calendar, start, end, attendee_only=None):
+ # Expand events into instances in the start/end range
+ results = []
+ calendar.getVEvents(
+ PyCalendarPeriod(
+ start=start,
+ end=end,
+ ),
+ results
+ )
+
+ # Need to do iCal fake master fixup
+ overrides = len(calendar.getComponents(definitions.cICalComponent_VEVENT)) > 1
+
+ # Create map of each attendee's instances with the instance id (start time) and attendee part-stat
+ attendees = {}
+ for item in results:
+
+ # Fake master fixup
+ if overrides:
+ if not item.getOwner().isRecurrenceInstance():
+ if item.getOwner().getRecurrenceSet() is None or not item.getOwner().getRecurrenceSet().hasRecurrence():
+ continue
+
+ # Get Status - ignore cancelled events
+ status = item.getOwner().loadValueString(definitions.cICalProperty_STATUS)
+ cancelled = status == definitions.cICalProperty_STATUS_CANCELLED
+
+ # Get instance start
+ item.getInstanceStart().adjustToUTC()
+ instance_id = item.getInstanceStart().getText()
+
+ props = item.getOwner().getProperties().get(definitions.cICalProperty_ATTENDEE, [])
+ for prop in props:
+ caladdr = prop.getCalAddressValue().getValue()
+ if caladdr.startswith("urn:uuid:"):
+ caladdr = caladdr[9:]
+ else:
+ continue
+ if attendee_only is not None and attendee_only != caladdr:
+ continue
+ if cancelled:
+ partstat = "CANCELLED"
+ else:
+ if not prop.hasAttribute(definitions.cICalAttribute_PARTSTAT):
+ partstat = definitions.cICalAttribute_PARTSTAT_NEEDSACTION
+ else:
+ partstat = prop.getAttributeValue(definitions.cICalAttribute_PARTSTAT)
+
+ attendees.setdefault(caladdr, set()).add((instance_id, partstat))
+
+ return attendees
+
+
+ def allCancelled(self, attendeesStatus):
+ # Check whether attendees have all instances cancelled
+ all_cancelled = True
+ for _ignore_guid, states in attendeesStatus.iteritems():
+ for _ignore_instance_id, partstat in states:
+ if partstat not in ("CANCELLED",):
+ all_cancelled = False
+ break
+ if not all_cancelled:
+ break
+ return all_cancelled
+
+
+ def directoryService(self):
+ """
+ Get an appropriate directory service for this L{CalVerifyService}'s
+ configuration, creating one first if necessary.
+ """
+ if self._directory is None:
+ self._directory = directoryFromConfig(self.config)
+ return self._directory
+
+
+ def stopService(self):
+ """
+ Stop the service. Nothing to do; everything should be finished by this
+ time.
+ """
+ # TODO: stopping this service mid-export should really stop the export
+ # loop, but this is not implemented because nothing will actually do it
+ # except hitting ^C (which also calls reactor.stop(), so that will exit
+ # anyway).
+
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+ """
+ Do the export.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+ options = CalVerifyOptions()
+ options.parseOptions(argv[1:])
+ try:
+ output = options.openOutput()
+ except IOError, e:
+ stderr.write("Unable to open output file for writing: %s\n" % (e))
+ sys.exit(1)
+ def makeService(store):
+ from twistedcaldav.config import config
+ return CalVerifyService(store, options, output, reactor, config)
+ utilityMain(options['config'], makeService, reactor)
+
+if __name__ == '__main__':
+ main()
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/changeip_calendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/changeip_calendar.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/changeip_calendar.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -103,6 +103,7 @@
("Notifications", "Services", "XMPPNotifier", "ServiceAddress"),
("Scheduling", "iMIP", "Receiving", "Server"),
("Scheduling", "iMIP", "Sending", "Server"),
+ ("Scheduling", "iMIP", "Sending", "Address"),
("ServerHostName",),
)
Copied: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/cmdline.py (from rev 7695, CalendarServer/trunk/calendarserver/tools/cmdline.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/cmdline.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/cmdline.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,77 @@
+##
+# Copyright (c) 2005-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.
+##
+
+"""
+Shared main-point between utilities.
+"""
+
+import sys
+
+from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
+from calendarserver.tools.util import loadConfig, autoDisableMemcached
+from twistedcaldav.config import ConfigurationError
+
+# TODO: direct unit tests for this function.
+
+def utilityMain(configFileName, serviceClass, reactor=None):
+ """
+ Shared main-point for utilities.
+
+ This function will:
+
+ - Load the configuration file named by C{configFileName},
+ - launch a L{CalDAVServiceMaker}'s with the C{ProcessType} of
+ C{"Utility"}
+ - run the reactor, with start/stop events hooked up to the service's
+ C{startService}/C{stopService} methods.
+
+ It is C{serviceClass}'s responsibility to stop the reactor when it's
+ complete.
+
+ @param configFileName: the name of the configuration file to load.
+ @type configuration: C{str}
+
+ @param serviceClass: a 1-argument callable which takes an object that
+ provides L{ICalendarStore} and/or L{IAddressbookStore} and returns an
+ L{IService}.
+
+ @param reactor: if specified, the L{IReactorTime} / L{IReactorThreads} /
+ L{IReactorTCP} (etc) provider to use. If C{None}, the default reactor
+ will be imported and used.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+ try:
+ config = loadConfig(configFileName)
+
+ config.ProcessType = "Utility"
+ config.UtilityServiceClass = serviceClass
+
+ autoDisableMemcached(config)
+
+ maker = CalDAVServiceMaker()
+ options = CalDAVOptions
+ service = maker.makeService(options)
+
+ reactor.addSystemEventTrigger("during", "startup", service.startService)
+ reactor.addSystemEventTrigger("before", "shutdown", service.stopService)
+
+ except ConfigurationError, e:
+ sys.stderr.write("Error: %s\n" % (e,))
+ return
+
+ reactor.run()
+
Copied: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/dbinspect.py (from rev 7695, CalendarServer/trunk/calendarserver/tools/dbinspect.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/dbinspect.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/dbinspect.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,568 @@
+#!/usr/bin/env python
+# -*- test-case-name: calendarserver.tools.test.test_calverify -*-
+##
+# Copyright (c) 2011 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.
+##
+
+"""
+This tool allows data in the database to be directly inspected using a set
+of simple commands.
+"""
+
+from calendarserver.tap.util import directoryFromConfig
+from calendarserver.tools import tables
+from calendarserver.tools.cmdline import utilityMain
+from twext.enterprise.dal.syntax import Select, Parameter, Count, Delete
+from twisted.application.service import Service
+from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.python.filepath import FilePath
+from twisted.python.reflect import namedClass
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options
+from twistedcaldav.config import config
+from twistedcaldav.directory import calendaruserproxy
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from txdav.common.datastore.sql_tables import schema, _BIND_MODE_OWN
+import os
+import sys
+import traceback
+
+def usage(e=None):
+ if e:
+ print e
+ print ""
+ try:
+ DBInspectOptions().opt_help()
+ except SystemExit:
+ pass
+ if e:
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
+
+description = '\n'.join(
+ wordWrap(
+ """
+ Usage: calendarserver_calverify [options] [input specifiers]\n
+ """,
+ int(os.environ.get('COLUMNS', '80'))
+ )
+)
+
+class DBInspectOptions(Options):
+ """
+ Command-line options for 'calendarserver_dbinspect'
+ """
+
+ synopsis = description
+
+ optFlags = [
+ ['verbose', 'v', "Verbose logging."],
+ ]
+
+ optParameters = [
+ ['config', 'f', DEFAULT_CONFIG_FILE, "Specify caldavd.plist configuration path."],
+ ]
+
+ def __init__(self):
+ super(DBInspectOptions, self).__init__()
+ self.outputName = '-'
+
+def UserNameFromUID(txn, uid):
+ record = txn._directory.recordWithGUID(uid)
+ return record.shortNames[0] if record else "(%s)" % (uid,)
+
+class Cmd(object):
+
+ _name = None
+
+ @classmethod
+ def name(cls):
+ return cls._name
+
+ def doIt(self, txn):
+ raise NotImplementedError
+
+class CalendarHomes(Cmd):
+
+ _name = "List Calendar Homes"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+ uids = yield self.getAllHomeUIDs(txn)
+
+ # Print table of results
+ missing = 0
+ table = tables.Table()
+ table.addHeader(("Owner UID", "Short Name"))
+ for uid in sorted(uids):
+ shortname = UserNameFromUID(txn, uid)
+ if shortname.startswith("("):
+ missing += 1
+ table.addRow((
+ uid,
+ shortname,
+ ))
+
+ print "\n"
+ print "Calendar Homes (total=%d, missing=%d):\n" % (len(uids), missing,)
+ table.printTable()
+
+ @inlineCallbacks
+ def getAllHomeUIDs(self, txn):
+ ch = schema.CALENDAR_HOME
+ rows = (yield Select(
+ [ch.OWNER_UID,],
+ From=ch,
+ ).on(txn))
+ returnValue(tuple([row[0] for row in rows]))
+
+
+class Calendars(Cmd):
+
+ _name = "List Calendars"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+ uids = yield self.getCalendars(txn)
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("Owner UID", "Short Name", "Calendar", "Resources"))
+ for uid, calname, count in sorted(uids, key=lambda x:(x[0], x[1])):
+ shortname = UserNameFromUID(txn, uid)
+ table.addRow((
+ uid,
+ shortname,
+ calname,
+ count
+ ))
+
+ print "\n"
+ print "Calendars with resource count (total=%d):\n" % (len(uids),)
+ table.printTable()
+
+ @inlineCallbacks
+ def getCalendars(self, txn):
+ ch = schema.CALENDAR_HOME
+ cb = schema.CALENDAR_BIND
+ co = schema.CALENDAR_OBJECT
+ rows = (yield Select(
+ [
+ ch.OWNER_UID,
+ cb.CALENDAR_RESOURCE_NAME,
+ Count(co.RESOURCE_ID),
+ ],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN)).join(
+ co, type="left", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID)),
+ GroupBy=(ch.OWNER_UID, cb.CALENDAR_RESOURCE_NAME)
+ ).on(txn))
+ returnValue(tuple(rows))
+
+
+class Events(Cmd):
+
+ _name = "List Events"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+ uids = yield self.getEvents(txn)
+
+ # Print table of results
+ table = tables.Table()
+ table.addHeader(("Owner UID", "Short Name", "Calendar", "ID", "Type", "UID"))
+ for uid, calname, id, caltype, caluid in sorted(uids, key=lambda x:(x[0], x[1])):
+ shortname = UserNameFromUID(txn, uid)
+ table.addRow((
+ uid,
+ shortname,
+ calname,
+ id,
+ caltype,
+ caluid
+ ))
+
+ print "\n"
+ print "Calendar events (total=%d):\n" % (len(uids),)
+ table.printTable()
+
+ @inlineCallbacks
+ def getEvents(self, txn):
+ ch = schema.CALENDAR_HOME
+ cb = schema.CALENDAR_BIND
+ co = schema.CALENDAR_OBJECT
+ rows = (yield Select(
+ [
+ ch.OWNER_UID,
+ cb.CALENDAR_RESOURCE_NAME,
+ co.RESOURCE_ID,
+ co.ICALENDAR_TYPE,
+ co.ICALENDAR_UID,
+ ],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN)).join(
+ co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID)),
+ ).on(txn))
+ returnValue(tuple(rows))
+
+class Event(Cmd):
+
+ _name = "Get Event Data by Resource-ID"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+
+ rid = raw_input("Resource-ID: ")
+ try:
+ int(rid)
+ except ValueError:
+ print 'Resource ID must be an integer'
+ returnValue(None)
+ data = yield self.getData(txn, rid)
+ if data:
+ print "\n"
+ print data
+ else:
+ print "Could not find resource"
+
+ @inlineCallbacks
+ def getData(self, txn, rid):
+ co = schema.CALENDAR_OBJECT
+ rows = (yield Select(
+ [
+ co.ICALENDAR_TEXT,
+ ],
+ From=co,
+ Where=(co.RESOURCE_ID == Parameter("ResourceID")),
+ ).on(txn, **{"ResourceID": rid}))
+ returnValue(rows[0][0] if rows else None)
+
+class EventsByUID(Cmd):
+
+ _name = "Get Event Data by iCalendar UID"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+
+ uid = raw_input("UID: ")
+ rows = yield self.getData(txn, uid)
+ if rows:
+ for owner, calendar, resource_id, resource, created, modified, data in rows:
+ shortname = UserNameFromUID(txn, owner)
+ table = tables.Table()
+ table.addRow(("User Name:", shortname,))
+ table.addRow(("Calendar:", calendar,))
+ table.addRow(("Resource Name:", resource))
+ table.addRow(("Resource ID:", resource_id))
+ table.addRow(("Created", created))
+ table.addRow(("Modified", modified))
+ print "\n"
+ table.printTable()
+ print data
+ else:
+ print "Could not find icalendar data"
+
+ @inlineCallbacks
+ def getData(self, txn, uid):
+ ch = schema.CALENDAR_HOME
+ cb = schema.CALENDAR_BIND
+ co = schema.CALENDAR_OBJECT
+ rows = (yield Select(
+ [
+ ch.OWNER_UID,
+ cb.CALENDAR_RESOURCE_NAME,
+ co.RESOURCE_ID,
+ co.RESOURCE_NAME,
+ co.CREATED,
+ co.MODIFIED,
+ co.ICALENDAR_TEXT,
+ ],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN)).join(
+ co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID)),
+ Where=(co.ICALENDAR_UID == Parameter("UID")),
+ ).on(txn, **{"UID": uid}))
+ returnValue(tuple(rows))
+
+
+class EventsByContent(Cmd):
+
+ _name = "Get Event Data by Searching its Text Data"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+
+ uid = raw_input("Search for: ")
+ rows = yield self.getData(txn, uid)
+ if rows:
+ for owner, calendar, resource_id, resource, created, modified, data in rows:
+ shortname = UserNameFromUID(txn, owner)
+ table = tables.Table()
+ table.addRow(("User Name:", shortname,))
+ table.addRow(("Calendar:", calendar,))
+ table.addRow(("Resource Name:", resource))
+ table.addRow(("Resource ID:", resource_id))
+ table.addRow(("Created", created))
+ table.addRow(("Modified", modified))
+ print "\n"
+ table.printTable()
+ print data
+ else:
+ print "Could not find icalendar data"
+
+ @inlineCallbacks
+ def getData(self, txn, text):
+ ch = schema.CALENDAR_HOME
+ cb = schema.CALENDAR_BIND
+ co = schema.CALENDAR_OBJECT
+ rows = (yield Select(
+ [
+ ch.OWNER_UID,
+ cb.CALENDAR_RESOURCE_NAME,
+ co.RESOURCE_ID,
+ co.RESOURCE_NAME,
+ co.CREATED,
+ co.MODIFIED,
+ co.ICALENDAR_TEXT,
+ ],
+ From=ch.join(
+ cb, type="inner", on=(ch.RESOURCE_ID == cb.CALENDAR_HOME_RESOURCE_ID).And(
+ cb.BIND_MODE == _BIND_MODE_OWN)).join(
+ co, type="inner", on=(cb.CALENDAR_RESOURCE_ID == co.CALENDAR_RESOURCE_ID)),
+ Where=(co.ICALENDAR_TEXT.Contains(Parameter("Text"))),
+ ).on(txn, **{"Text": text}))
+ returnValue(tuple(rows))
+
+
+class Purge(Cmd):
+
+ _name = "Purge all data from tables"
+
+ @inlineCallbacks
+ def doIt(self, txn):
+
+
+ if raw_input("Do you really want to remove all data [y/n]: ")[0].lower() != 'y':
+ print "No data removed"
+ returnValue(None)
+
+ wipeout = (
+ # These are ordered in such a way as to ensure key constraints are not
+ # violated as data is removed
+
+ schema.RESOURCE_PROPERTY,
+
+ schema.CALENDAR_OBJECT_REVISIONS,
+
+ schema.CALENDAR,
+ #schema.CALENDAR_BIND, - cascades
+ #schema.CALENDAR_OBJECT, - cascades
+ #schema.TIME_RANGE, - cascades
+ #schema.TRANSPARENCY, - cascades
+
+
+ schema.CALENDAR_HOME,
+ #schema.CALENDAR_HOME_METADATA - cascades
+ schema.INVITE,
+ schema.ATTACHMENT,
+
+ schema.ADDRESSBOOK_OBJECT_REVISIONS,
+
+ schema.ADDRESSBOOK,
+ #schema.ADDRESSBOOK_BIND, - cascades
+ #schema.ADDRESSBOOK_OBJECT, - cascades
+
+ schema.ADDRESSBOOK_HOME,
+ #schema.ADDRESSBOOK_HOME_METADATA, - cascades
+
+ schema.NOTIFICATION_HOME,
+ schema.NOTIFICATION,
+ #schema.NOTIFICATION_OBJECT_REVISIONS - cascades,
+ )
+
+ for tableschema in wipeout:
+ yield self.removeTableData(txn, tableschema)
+ print "Removed rows in table %s" % (tableschema,)
+
+ if calendaruserproxy.ProxyDBService is not None:
+ calendaruserproxy.ProxyDBService.clean() #@UndefinedVariable
+ print "Removed all proxies"
+ else:
+ print "No proxy database to clean."
+
+ fp = FilePath(config.AttachmentsRoot)
+ if fp.exists():
+ for child in fp.children():
+ child.remove()
+ print "Removed attachments."
+ else:
+ print "No attachments path to delete."
+
+ @inlineCallbacks
+ def removeTableData(self, txn, tableschema):
+ yield Delete(
+ From=tableschema,
+ Where=None # Deletes all rows
+ ).on(txn)
+
+
+class DBInspectService(Service, object):
+ """
+ Service which runs, exports the appropriate records, then stops the reactor.
+ """
+
+ def __init__(self, store, options, reactor, config):
+ super(DBInspectService, self).__init__()
+ self.store = store
+ self.options = options
+ self.reactor = reactor
+ self.config = config
+ self._directory = None
+ self.commands = []
+ self.commandMap = {}
+
+
+ def startService(self):
+ """
+ Start the service.
+ """
+ super(DBInspectService, self).startService()
+
+ # Register commands
+ self.registerCommand(CalendarHomes)
+ self.registerCommand(Calendars)
+ self.registerCommand(Events)
+ self.registerCommand(Event)
+ self.registerCommand(EventsByUID)
+ self.registerCommand(EventsByContent)
+ self.doDBInspect()
+
+
+ def registerCommand(self, cmd):
+ self.commands.append(cmd.name())
+ self.commandMap[cmd.name()] = cmd
+
+ @inlineCallbacks
+ def runCommandByPosition(self, position):
+ try:
+ yield self.runCommandByName(self.commands[position])
+ except IndexError:
+ print "Position %d not available" % (position,)
+ returnValue(None)
+
+ @inlineCallbacks
+ def runCommandByName(self, name):
+ try:
+ yield self.runCommand(self.commandMap[name])
+ except IndexError:
+ print "Unknown command: '%s'" % (name,)
+
+ @inlineCallbacks
+ def runCommand(self, cmd):
+ txn = self.store.newTransaction()
+ txn._directory = self.directoryService()
+ try:
+ yield cmd().doIt(txn)
+ yield txn.commit()
+ except Exception, e:
+ yield txn.abort()
+ print "Command '%s' failed because of: %s" % (cmd.name(), e,)
+ traceback.print_exc()
+
+ def printCommands(self):
+
+ print "\n<---- Commands ---->"
+ for ctr, name in enumerate(self.commands):
+ print "%d. %s" % (ctr+1, name,)
+ print "P. Purge\n"
+ print "Q. Quit\n"
+
+ @inlineCallbacks
+ def doDBInspect(self):
+ """
+ Poll for commands, stopping the reactor when done.
+ """
+
+ while True:
+ self.printCommands()
+ cmd = raw_input("Command: ")
+ if cmd.lower() == 'q':
+ break
+ if cmd.lower() == 'p':
+ yield self.runCommand(Purge)
+ else:
+ try:
+ position = int(cmd)
+ except ValueError:
+ print "Invalid command. Try again.\n"
+ continue
+
+ yield self.runCommandByPosition(position-1)
+
+ self.reactor.stop()
+
+
+ def directoryService(self):
+ """
+ Get an appropriate directory service for this L{DBInspectService}'s
+ configuration, creating one first if necessary.
+ """
+ if self._directory is None:
+ self._directory = directoryFromConfig(self.config)
+ proxydbClass = namedClass(config.ProxyDBService.type)
+ try:
+ calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+ except IOError:
+ print "Could not start proxydb service"
+ return self._directory
+
+
+ def stopService(self):
+ """
+ Stop the service. Nothing to do; everything should be finished by this
+ time.
+ """
+ # TODO: stopping this service mid-export should really stop the export
+ # loop, but this is not implemented because nothing will actually do it
+ # except hitting ^C (which also calls reactor.stop(), so that will exit
+ # anyway).
+
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+ """
+ Do the export.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+ options = DBInspectOptions()
+ options.parseOptions(argv[1:])
+ def makeService(store):
+ return DBInspectService(store, options, reactor, config)
+ utilityMain(options['config'], makeService, reactor)
+
+if __name__ == '__main__':
+ main()
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/export.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/export.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/export.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,7 +1,7 @@
#!/usr/bin/env python
-
+# -*- test-case-name: calendarserver.tools.test.test_export -*-
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -17,208 +17,357 @@
##
"""
-This tool reads calendar data from a series of inputs and generates a
-single iCalendar file which can be opened in many calendar
-applications.
+This tool reads calendar data from a series of inputs and generates a single
+iCalendar file which can be opened in many calendar applications.
-This can be used to quickly create an iCalendar file from a user's
-calendars.
+This can be used to quickly create an iCalendar file from a user's calendars.
-This tool requires access to the calendar server's configuration and
-data storage; it does not operate by talking to the server via the
-network. It therefore does not apply any of the access restrictions
-that the server would. As such, one should be midful that data
-exported via this tool may be sensitive.
+This tool requires access to the calendar server's configuration and data
+storage; it does not operate by talking to the server via the network. It
+therefore does not apply any of the access restrictions that the server would.
+As such, one should be mindful that data exported via this tool may be sensitive.
+
+Please also note that this is not an appropriate tool for backups, as there is
+data associated with users and calendars beyond the iCalendar as visible to the
+owner of that calendar, including DAV properties, information about sharing, and
+per-user data such as alarms.
+
"""
import os
import sys
-from getopt import getopt, GetoptError
-from os.path import dirname, abspath
+import itertools
-from twistedcaldav.config import ConfigurationError
-from twistedcaldav.ical import Component as iComponent, Property as iProperty
-from twistedcaldav.ical import iCalendarProductID
-from twistedcaldav.resource import isCalendarCollectionResource,\
- CalendarHomeResource
-from twistedcaldav.static import CalDAVFile
-from twistedcaldav.directory.directory import DirectoryService
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options, UsageError
+from twisted.python import log
+from twisted.internet.defer import inlineCallbacks, returnValue
-from calendarserver.tools.util import UsageError
-from calendarserver.tools.util import loadConfig, getDirectory, dummyDirectoryRecord, autoDisableMemcached
+from twistedcaldav.ical import Component
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+from calendarserver.tools.cmdline import utilityMain
+from twisted.application.service import Service
+from calendarserver.tap.util import directoryFromConfig
+
+
def usage(e=None):
if e:
print e
print ""
-
- name = os.path.basename(sys.argv[0])
- print "usage: %s [options] [input_specifiers]" % (name,)
- print ""
- print "Generate an iCalendar file containing the merged content of each calendar"
- print "collection read."
- print __doc__
- print "options:"
- print " -h --help: print this help and exit"
- print " -f --config: Specify caldavd.plist configuration path"
- print " -o --output: Specify output file path (default: '-', meaning stdout)"
- print ""
- print "input specifiers:"
- print " -c --collection: add a calendar collection"
- print " -H --home: add a calendar home (and all calendars within it)"
- print " -r --record: add a directory record's calendar home (format: 'recordType:shortName')"
- print " -u --user: add a user's calendar home (shorthand for '-r users:shortName')"
-
+ try:
+ ExportOptions().opt_help()
+ except SystemExit:
+ pass
if e:
sys.exit(64)
else:
sys.exit(0)
-def main():
- try:
- (optargs, args) = getopt(
- sys.argv[1:], "hf:o:c:H:r:u:", [
- "help",
- "config=",
- "output=",
- "collection=", "home=", "record=", "user=",
- ],
- )
- except GetoptError, e:
- usage(e)
- configFileName = None
- outputFileName = None
+description = '\n'.join(
+ wordWrap(
+ """
+ Usage: calendarserver_export [options] [input specifiers]\n
+ """ + __doc__,
+ int(os.environ.get('COLUMNS', '80'))
+ )
+)
- collections = set()
- calendarHomes = set()
- records = set()
+class ExportOptions(Options):
+ """
+ Command-line options for 'calendarserver_export'
- def checkExists(resource):
- if not resource.exists():
- sys.stderr.write("No such file: %s\n" % (resource.fp.path,))
- sys.exit(1)
+ @ivar exporters: a list of L{DirectoryExporter} objects which can identify the
+ calendars to export, given a directory service. This list is built by
+ parsing --record and --collection options.
+ """
- for opt, arg in optargs:
- if opt in ("-h", "--help"):
- usage()
+ synopsis = description
- elif opt in ("-f", "--config"):
- configFileName = arg
+ optParameters = [['config', 'f', DEFAULT_CONFIG_FILE,
+ "Specify caldavd.plist configuration path."]]
- elif opt in ("-o", "--output"):
- if arg == "-":
- outputFileName = None
- else:
- outputFileName = arg
+ def __init__(self):
+ super(ExportOptions, self).__init__()
+ self.exporters = []
+ self.outputName = '-'
- elif opt in ("-c", "--collection"):
- path = abspath(arg)
- collection = CalDAVFile(path)
- checkExists(collection)
- if not isCalendarCollectionResource(collection):
- sys.stderr.write("Not a calendar collection: %s\n" % (path,))
- sys.exit(1)
- collections.add(collection)
- elif opt in ("-H", "--home"):
- path = abspath(arg)
- parent = CalDAVFile(dirname(abspath(path)))
- calendarHome = CalendarHomeResource(arg, parent, dummyDirectoryRecord)
- checkExists(calendarHome)
- calendarHomes.add(calendarHome)
+ def opt_uid(self, uid):
+ """
+ Add a calendar home directly by its UID (which is usually a directory
+ service's GUID).
+ """
+ self.exporters.append(UIDExporter(uid))
- elif opt in ("-r", "--record"):
- try:
- recordType, shortName = arg.split(":", 1)
- if not recordType or not shortName:
- raise ValueError()
- except ValueError:
- sys.stderr.write("Invalid record identifier: %r\n" % (arg,))
- sys.exit(1)
- records.add((recordType, shortName))
+ def opt_record(self, recordName):
+ """
+ Add a directory record's calendar home (format: 'recordType:shortName').
+ """
+ recordType, shortName = recordName.split(":", 1)
+ self.exporters.append(DirectoryExporter(recordType, shortName))
- elif opt in ("-u", "--user"):
- records.add((DirectoryService.recordType_users, arg))
+ opt_r = opt_record
- if args:
- usage("Too many arguments: %s" % (" ".join(args),))
- if records:
- try:
- config = loadConfig(configFileName)
- config.directory = getDirectory()
- autoDisableMemcached(config)
- except ConfigurationError, e:
- sys.stdout.write("%s\n" % (e,))
- sys.exit(1)
+ def opt_collection(self, collectionName):
+ """
+ Add a calendar collection. This option must be passed after --record
+ (or a synonym, like --user). for example, to export user1's calendars
+ called 'meetings' and 'team', invoke 'calendarserver_export --user=user1
+ --collection=meetings --collection=team'.
+ """
+ self.exporters[-1].collections.append(collectionName)
- for record in records:
- recordType, shortName = record
- calendarHome = config.directory.calendarHomeForShortName(recordType, shortName)
- if not calendarHome:
- sys.stderr.write("No calendar home found for record: (%s)%s\n" % (recordType, shortName))
- sys.exit(1)
- calendarHomes.add(calendarHome)
+ opt_c = opt_collection
- for calendarHome in calendarHomes:
- for childName in calendarHome.listChildren():
- child = calendarHome.getChild(childName)
- if isCalendarCollectionResource(child):
- collections.add(child)
- try:
- calendar = iComponent("VCALENDAR")
- calendar.addProperty(iProperty("VERSION", "2.0"))
- calendar.addProperty(iProperty("PRODID", iCalendarProductID))
+ def opt_output(self, filename):
+ """
+ Specify output file path (default: '-', meaning stdout).
+ """
+ self.outputName = filename
- uids = set()
- tzids = set()
+ opt_o = opt_output
- for collection in collections:
- for name, uid, type in collection.index().indexedSearch(None):
- child = collection.getChild(name)
- childData = child.iCalendarText()
- try:
- childCalendar = iComponent.fromString(childData)
- except ValueError:
- continue
- assert childCalendar.name() == "VCALENDAR"
+ def opt_user(self, user):
+ """
+ Add a user's calendar home (shorthand for '-r users:shortName').
+ """
+ self.opt_record("users:" + user)
- if uid in uids:
- sys.stderr.write("Skipping duplicate event UID %r from %s\n" % (uid, collection.fp.path))
- continue
- else:
- uids.add(uid)
+ opt_u = opt_user
- for component in childCalendar.subcomponents():
- # Only insert VTIMEZONEs once
- if component.name() == "VTIMEZONE":
- tzid = component.propertyValue("TZID")
- if tzid in tzids:
- continue
- else:
- tzids.add(tzid)
- calendar.addComponent(component)
+ def openOutput(self):
+ """
+ Open the appropriate output file based on the '--output' option.
+ """
+ if self.outputName == '-':
+ return sys.stdout
+ else:
+ return open(self.outputName, 'wb')
- calendarData = str(calendar)
- if outputFileName:
- try:
- output = open(outputFileName, "w")
- except IOError, e:
- sys.stderr.write("Unable to open output file for writing %s: %s\n" % (outputFileName, e))
- sys.exit(1)
+
+class _ExporterBase(object):
+ """
+ Base exporter implementation that works from a home UID.
+
+ @ivar collections: A list of the names of collections that this exporter
+ should enumerate.
+
+ @type collections: C{list} of C{str}
+
+ """
+
+ def __init__(self):
+ self.collections = []
+
+
+ def getHomeUID(self, exportService):
+ """
+ Subclasses must implement this.
+ """
+ raise NotImplementedError()
+
+
+ @inlineCallbacks
+ def listCalendars(self, txn, exportService):
+ """
+ Enumerate all calendars based on the directory record and/or calendars
+ for this calendar home.
+ """
+ uid = self.getHomeUID(exportService)
+ home = yield txn.calendarHomeWithUID(uid, True)
+ result = []
+ if self.collections:
+ for collection in self.collections:
+ result.append((yield home.calendarWithName(collection)))
else:
- output = sys.stdout
+ for collection in (yield home.calendars()):
+ if collection.name() != 'inbox':
+ result.append(collection)
+ returnValue(result)
- output.write(calendarData)
+
+class UIDExporter(_ExporterBase):
+ """
+ An exporter that constructs a list of calendars based on the UID of the
+ home, and an optional list of calendar names.
+
+ @ivar uid: The UID of a calendar home to export.
+
+ @type uid: C{str}
+ """
+
+ def __init__(self, uid):
+ super(UIDExporter, self).__init__()
+ self.uid = uid
+
+
+ def getHomeUID(self, exportService):
+ return self.uid
+
+
+
+class DirectoryExporter(_ExporterBase):
+ """
+ An exporter that constructs a list of calendars based on the directory
+ services record ID of the home, and an optional list of calendar names.
+
+ @ivar recordType: The directory record type to export. For example:
+ 'users'.
+
+ @type recordType: C{str}
+
+ @ivar shortName: The shortName of the directory record to export, according
+ to C{recordType}.
+
+ @type shortName: C{str}
+ """
+
+ def __init__(self, recordType, shortName):
+ super(DirectoryExporter, self).__init__()
+ self.recordType = recordType
+ self.shortName = shortName
+
+
+ def getHomeUID(self, exportService):
+ """
+ Retrieve the home UID.
+ """
+ directory = exportService.directoryService()
+ record = directory.recordWithShortName(self.recordType, self.shortName)
+ return record.guid
+
+
+
+ at inlineCallbacks
+def exportToFile(calendars, fileobj):
+ """
+ Export some calendars to a file as their owner would see them.
+
+ @param calendars: an iterable of L{ICalendar} providers (or L{Deferred}s of
+ same).
+
+ @param fileobj: an object with a C{write} method that will accept some
+ iCalendar data.
+
+ @return: a L{Deferred} which fires when the export is complete. (Note that
+ the file will not be closed.)
+ @rtype: L{Deferred} that fires with C{None}
+ """
+ comp = Component.newCalendar()
+ for calendar in calendars:
+ calendar = yield calendar
+ for obj in (yield calendar.calendarObjects()):
+ evt = yield obj.filteredComponent(
+ calendar.ownerCalendarHome().uid(), True)
+ for sub in evt.subcomponents():
+ if sub.name() != 'VTIMEZONE':
+ # Omit all VTIMEZONE components here - we will include them later
+ # when we serialize the whole calendar.
+ comp.addComponent(sub)
+
+ fileobj.write(comp.getTextWithTimezones(True))
+
+
+
+class ExporterService(Service, object):
+ """
+ Service which runs, exports the appropriate records, then stops the reactor.
+ """
+
+ def __init__(self, store, options, output, reactor, config):
+ super(ExporterService, self).__init__()
+ self.store = store
+ self.options = options
+ self.output = output
+ self.reactor = reactor
+ self.config = config
+ self._directory = None
+
+
+ def startService(self):
+ """
+ Start the service.
+ """
+ super(ExporterService, self).startService()
+ self.doExport()
+
+
+ @inlineCallbacks
+ def doExport(self):
+ """
+ Do the export, stopping the reactor when done.
+ """
+ txn = self.store.newTransaction()
+ try:
+ allCalendars = itertools.chain(
+ *[(yield exporter.listCalendars(txn, self)) for exporter in
+ self.options.exporters]
+ )
+ yield exportToFile(allCalendars, self.output)
+ yield txn.commit()
+ # TODO: should be read-only, so commit/abort shouldn't make a
+ # difference. commit() for now, in case any transparent cache /
+ # update stuff needed to happen, don't want to undo it.
+ self.output.close()
+ except:
+ log.err()
+
+ self.reactor.stop()
+
+
+ def directoryService(self):
+ """
+ Get an appropriate directory service for this L{ExporterService}'s
+ configuration, creating one first if necessary.
+ """
+ if self._directory is None:
+ self._directory = directoryFromConfig(self.config)
+ return self._directory
+
+
+ def stopService(self):
+ """
+ Stop the service. Nothing to do; everything should be finished by this
+ time.
+ """
+ # TODO: stopping this service mid-export should really stop the export
+ # loop, but this is not implemented because nothing will actually do it
+ # except hitting ^C (which also calls reactor.stop(), so that will exit
+ # anyway).
+
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+ """
+ Do the export.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+
+ options = ExportOptions()
+ try:
+ options.parseOptions(argv[1:])
except UsageError, e:
usage(e)
-if __name__ == "__main__":
- main()
+ try:
+ output = options.openOutput()
+ except IOError, e:
+ stderr.write("Unable to open output file for writing: %s\n" %
+ (e))
+ sys.exit(1)
+
+ def makeService(store):
+ from twistedcaldav.config import config
+ return ExporterService(store, options, output, reactor, config)
+
+ utilityMain(options["config"], makeService, reactor)
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/notifications.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/notifications.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/notifications.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -47,10 +47,17 @@
print " -f --config <path>: Specify caldavd.plist configuration path"
print " -h --help: print this help and exit"
print " -H --host <hostname>: calendar server host name"
+ print " -n --node <pubsub node>: pubsub node to subscribe to *"
print " -p --port <port number>: calendar server port number"
print " -s --ssl: use https (default is http)"
print " -v --verbose: print additional information including XMPP traffic"
print ""
+ print " * The --node option is only required for calendar servers that"
+ print " don't advertise the push-transports DAV property (such as a Snow"
+ print " Leopard server). In this case, --host should specify the name"
+ print " of the XMPP server and --port should specify the port XMPP is"
+ print " is listening on."
+ print ""
if e:
sys.stderr.write("%s\n" % (e,))
@@ -62,11 +69,12 @@
def main():
try:
(optargs, args) = getopt(
- sys.argv[1:], "a:f:hH:p:sv", [
+ sys.argv[1:], "a:f:hH:n:p:sv", [
"admin=",
"config=",
"help",
"host=",
+ "node=",
"port=",
"ssl",
"verbose",
@@ -78,6 +86,7 @@
admin = None
configFileName = None
host = None
+ nodes = None
port = None
useSSL = False
verbose = False
@@ -91,6 +100,8 @@
admin = arg
elif opt in ("-H", "--host"):
host = arg
+ elif opt in ("-n", "--node"):
+ nodes = [arg]
elif opt in ("-p", "--port"):
port = int(arg)
elif opt in ("-s", "--ssl"):
@@ -126,8 +137,8 @@
password = getpass("Password for %s: " % (username,))
admin = username
- monitorService = PushMonitorService(useSSL, host, port, admin, username,
- password, verbose)
+ monitorService = PushMonitorService(useSSL, host, port, nodes, admin,
+ username, password, verbose)
reactor.addSystemEventTrigger("during", "startup",
monitorService.startService)
reactor.addSystemEventTrigger("before", "shutdown",
@@ -305,10 +316,12 @@
using XMPP and monitored for updates.
"""
- def __init__(self, useSSL, host, port, authname, username, password, verbose):
+ def __init__(self, useSSL, host, port, nodes, authname, username, password,
+ verbose):
self.useSSL = useSSL
self.host = host
self.port = port
+ self.nodes = nodes
self.authname = authname
self.username = username
self.password = password
@@ -317,24 +330,31 @@
@inlineCallbacks
def startService(self):
try:
- paths = set()
- principal = "/principals/users/%s/" % (self.username,)
- name, homes = (yield self.getPrincipalDetails(principal))
- if self.verbose:
- print name, homes
- for home in homes:
- paths.add(home)
- for principal in (yield self.getProxyFor()):
- name, homes = (yield self.getPrincipalDetails(principal,
- includeCardDAV=False))
+ subscribeNodes = { }
+ if self.nodes is None:
+ paths = set()
+ principal = "/principals/users/%s/" % (self.username,)
+ name, homes = (yield self.getPrincipalDetails(principal))
if self.verbose:
print name, homes
for home in homes:
paths.add(home)
- subscribeNodes = { }
- for path in paths:
- host, port, nodes = (yield self.getPushInfo(path))
- subscribeNodes.update(nodes)
+ for principal in (yield self.getProxyFor()):
+ name, homes = (yield self.getPrincipalDetails(principal,
+ includeCardDAV=False))
+ if self.verbose:
+ print name, homes
+ for home in homes:
+ paths.add(home)
+ for path in paths:
+ host, port, nodes = (yield self.getPushInfo(path))
+ subscribeNodes.update(nodes)
+ else:
+ for node in self.nodes:
+ subscribeNodes[node] = ("Unknown", "Unknown", "Unknown")
+ host = self.host
+ port = self.port
+
if subscribeNodes:
self.startMonitoring(host, port, subscribeNodes)
else:
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/principals.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/principals.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -35,7 +35,6 @@
from twistedcaldav.config import config, ConfigurationError
from twistedcaldav.directory.directory import UnknownRecordTypeError, DirectoryError
-from twistedcaldav.directory import augment
from calendarserver.tools.util import loadConfig, getDirectory, setupMemcached, booleanArgument, checkDirectory
@@ -806,9 +805,10 @@
else:
record = directory.updateRecord(recordType, **kwargs)
- augmentRecord = (yield augment.AugmentService.getAugmentRecord(kwargs['guid'], recordType))
+ augmentService = directory.serviceForRecordType(recordType).augmentService
+ augmentRecord = (yield augmentService.getAugmentRecord(kwargs['guid'], recordType))
augmentRecord.autoSchedule = autoSchedule
- (yield augment.AugmentService.addAugmentRecords([augmentRecord]))
+ (yield augmentService.addAugmentRecords([augmentRecord]))
directory.updateRecord(recordType, **kwargs)
returnValue(record)
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/purge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/purge.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/purge.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-
+# -*- test-case-name: calendarserver.tools.test.test_purge -*-
##
# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
#
@@ -19,12 +19,18 @@
import os
import sys
from errno import ENOENT, EACCES
-
from getopt import getopt, GetoptError
+from pycalendar.datetime import PyCalendarDateTime
+
from twisted.application.service import Service
from twisted.internet import reactor
from twisted.internet.defer import inlineCallbacks, returnValue
+
+from twext.python.log import Logger
+from twext.web2.dav import davxml
+from twext.web2.responsecode import NO_CONTENT
+
from twistedcaldav import caldavxml
from twistedcaldav.caldavxml import TimeRange
from twistedcaldav.config import config, ConfigurationError
@@ -33,16 +39,11 @@
from twistedcaldav.method.put_common import StoreCalendarObjectResource
from twistedcaldav.query import calendarqueryfilter
-from twext.python.log import Logger
-from twext.web2.dav import davxml
-from twext.web2.responsecode import NO_CONTENT
-
-from calendarserver.tap.caldav import CalDAVServiceMaker, CalDAVOptions
from calendarserver.tap.util import FakeRequest
from calendarserver.tap.util import getRootResource
+
+from calendarserver.tools.cmdline import utilityMain
from calendarserver.tools.principals import removeProxy
-from calendarserver.tools.util import loadConfig
-from pycalendar.datetime import PyCalendarDateTime
log = Logger()
@@ -205,27 +206,7 @@
-def shared_main(configFileName, serviceClass):
- try:
- loadConfig(configFileName)
-
- config.ProcessType = "Utility"
- config.UtilityServiceClass = serviceClass
-
- maker = CalDAVServiceMaker()
- options = CalDAVOptions
- service = maker.makeService(options)
-
- reactor.addSystemEventTrigger("during", "startup", service.startService)
- reactor.addSystemEventTrigger("before", "shutdown", service.stopService)
-
- except ConfigurationError, e:
- sys.stderr.write("Error: %s\n" % (e,))
- return
-
- reactor.run()
-
def main_purge_events():
try:
@@ -295,7 +276,7 @@
PurgeOldEventsService.dryrun = dryrun
PurgeOldEventsService.verbose = verbose
- shared_main(
+ utilityMain(
configFileName,
PurgeOldEventsService,
)
@@ -357,7 +338,7 @@
PurgeOrphanedAttachmentsService.dryrun = dryrun
PurgeOrphanedAttachmentsService.verbose = verbose
- shared_main(
+ utilityMain(
configFileName,
PurgeOrphanedAttachmentsService,
)
@@ -406,7 +387,7 @@
PurgePrincipalService.verbose = verbose
- shared_main(
+ utilityMain(
configFileName,
PurgePrincipalService
)
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/resources.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/resources.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/resources.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -26,7 +26,6 @@
from twisted.internet.defer import inlineCallbacks
from twisted.python.util import switchUID
from twistedcaldav.config import config, ConfigurationError
-from twistedcaldav.directory import augment
from twistedcaldav.directory.appleopendirectory import OpenDirectoryService
from twistedcaldav.directory.directory import DirectoryService, DirectoryError
from twistedcaldav.directory.xmlfile import XMLDirectoryService
@@ -217,7 +216,7 @@
autoSchedule = autoSchedules.get(guid, 1)
else:
autoSchedule = True
- augmentRecord = (yield augment.AugmentService.getAugmentRecord(guid, recordType))
+ augmentRecord = (yield destService.augmentService.getAugmentRecord(guid, recordType))
augmentRecord.autoSchedule = autoSchedule
augmentRecords.append(augmentRecord)
@@ -233,7 +232,7 @@
destService.createRecords(directoryRecords)
- (yield augment.AugmentService.addAugmentRecords(augmentRecords))
+ (yield destService.augmentService.addAugmentRecords(augmentRecords))
Copied: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/shell.py (from rev 7695, CalendarServer/trunk/calendarserver/tools/shell.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/shell.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/shell.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,413 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2011 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.
+##
+
+"""
+Interactive shell for navigating the data store.
+"""
+
+import os
+import sys
+import traceback
+from shlex import shlex
+
+#from twisted.python import log
+from twisted.python.text import wordWrap
+from twisted.python.usage import Options, UsageError
+from twisted.internet.defer import succeed, maybeDeferred
+from twisted.conch.stdio import runWithProtocol as shellWithProtocol
+from twisted.conch.recvline import HistoricRecvLine
+from twisted.application.service import Service
+
+from txdav.common.icommondatastore import NotFoundError
+
+from twistedcaldav.stdconfig import DEFAULT_CONFIG_FILE
+
+from calendarserver.tools.cmdline import utilityMain
+
+
+def usage(e=None):
+ if e:
+ print e
+ print ""
+ try:
+ ShellOptions().opt_help()
+ except SystemExit:
+ pass
+ if e:
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
+
+class ShellOptions(Options):
+ """
+ Command line options for "calendarserver_shell".
+ """
+ synopsis = "\n".join(
+ wordWrap(
+ """
+ Usage: calendarserver_shell [options]\n
+ """ + __doc__,
+ int(os.environ.get("COLUMNS", "80"))
+ )
+ )
+
+ optParameters = [
+ ["config", "f", DEFAULT_CONFIG_FILE, "Specify caldavd.plist configuration path."],
+ ]
+
+ def __init__(self):
+ super(ShellOptions, self).__init__()
+
+
+class ShellService(Service, object):
+ def __init__(self, store, options, reactor, config):
+ super(ShellService, self).__init__()
+ self.store = store
+ self.options = options
+ self.reactor = reactor
+ self.config = config
+
+ def startService(self):
+ """
+ Start the service.
+ """
+ super(ShellService, self).startService()
+ shellWithProtocol(lambda: ShellProtocol(self.store))
+ self.reactor.stop()
+
+ def stopService(self):
+ """
+ Stop the service.
+ """
+
+
+class ShellProtocol(HistoricRecvLine):
+ """
+ Data store shell protocol.
+ """
+
+ # FIXME:
+ # * Received lines are being echoed; find out why and stop it.
+ # * Backspace transposes characters in the terminal.
+
+ ps = ("ds% ", "... ")
+
+ def __init__(self, store):
+ HistoricRecvLine.__init__(self)
+ self.wd = RootDirectory(store)
+
+ def connectionMade(self):
+ HistoricRecvLine.connectionMade(self)
+
+ CTRL_C = "\x03"
+ CTRL_D = "\x04"
+ CTRL_L = "\x0c"
+ CTRL_BACKSLASH = "\x1c"
+
+ self.keyHandlers[CTRL_C ] = self.handle_INT
+ self.keyHandlers[CTRL_D ] = self.handle_EOF
+ self.keyHandlers[CTRL_L ] = self.handle_FF
+ self.keyHandlers[CTRL_BACKSLASH] = self.handle_QUIT
+
+ def handle_INT(self):
+ """
+ Handle ^C as an interrupt keystroke by resetting the current input
+ variables to their initial state.
+ """
+ self.pn = 0
+ self.lineBuffer = []
+ self.lineBufferIndex = 0
+ self.interpreter.resetBuffer()
+
+ self.terminal.nextLine()
+ self.terminal.write("KeyboardInterrupt")
+ self.terminal.nextLine()
+ self.terminal.write(self.ps[self.pn])
+
+ def handle_EOF(self):
+ if self.lineBuffer:
+ self.terminal.write("\a")
+ else:
+ self.handle_QUIT()
+
+ def handle_FF(self):
+ """
+ Handle a "form feed" byte - generally used to request a screen
+ refresh/redraw.
+ """
+ self.terminal.eraseDisplay()
+ self.terminal.cursorHome()
+ self.drawInputLine()
+
+ def handle_QUIT(self):
+ self.terminal.loseConnection()
+
+ def prompt(self):
+ pass
+
+ def lineReceived(self, line):
+ try:
+ lexer = shlex(line)
+ lexer.whitespace_split = True
+
+ tokens = []
+ while True:
+ token = lexer.get_token()
+ if not token:
+ break
+ tokens.append(token)
+
+ if tokens:
+ cmd = tokens.pop(0)
+ #print "Arguments: %r" % (tokens,)
+
+ m = getattr(self, "cmd_%s" % (cmd,), None)
+ if m:
+ def onError(f):
+ print "Error: %s" % (f.getErrorMessage(),)
+ print "-"*80
+ f.printTraceback()
+ print "-"*80
+
+ d = maybeDeferred(m, tokens)
+ d.addCallback(lambda _: self.prompt)
+ d.addErrback(onError)
+ return d
+ else:
+ print "Unknown command: %s" % (cmd,)
+
+ except Exception, e:
+ print "Error: %s" % (e,)
+ print "-"*80
+ traceback.print_exc()
+ print "-"*80
+
+ def cmd_pwd(self, tokens):
+ """
+ Print working directory.
+ """
+ if tokens:
+ print "Unknown arguments: %s" % (tokens,)
+ return
+ print self.wd
+
+ def cmd_cd(self, tokens):
+ """
+ Change working directory.
+ """
+ if tokens:
+ dirname = tokens.pop(0)
+ else:
+ return
+
+ if tokens:
+ print "Unknown arguments: %s" % (tokens,)
+ return
+
+ path = dirname.split("/")
+
+ def notFound(f):
+ f.trap(NotFoundError)
+ print "No such directory: %s" % (dirname,)
+
+ def setWD(wd):
+ self.wd = wd
+
+ d = self.wd.locate(path)
+ d.addCallback(setWD)
+ d.addErrback(notFound)
+ return d
+
+ def cmd_ls(self, tokens):
+ """
+ List working directory.
+ """
+ if tokens:
+ print "Unknown arguments: %s" % (tokens,)
+ return
+
+ for name in self.wd.list():
+ print name
+
+ def cmd_info(self, tokens):
+ """
+ Print information about working directory.
+ """
+ d = self.wd.describe()
+ d.addCallback(lambda x: sys.stdout.write(x))
+ return d
+
+ def cmd_exit(self, tokens):
+ """
+ Exit the shell.
+ """
+ self.terminal.loseConnection()
+ # FIXME: This is insufficient.
+
+ def cmd_python(self, tokens):
+ """
+ Switch to a python prompt.
+ """
+ # Crazy idea #19568: switch to an interactive python prompt
+ # with self exposed in globals.
+ raise NotImplementedError()
+
+
+class Directory(object):
+ """
+ Location in virtual data hierarchy.
+ """
+ def __init__(self, store, path):
+ assert type(path) is tuple
+
+ self.store = store
+ self.path = path
+
+ def __str__(self):
+ return "/" + "/".join(self.path)
+
+ def describe(self):
+ return succeed(str(self))
+
+ def locate(self, path):
+ if not path:
+ return succeed(RootDirectory(self.store))
+
+ name = path[0]
+ if not name:
+ return succeed(self.locate(path[1:]))
+
+ path = list(path)
+
+ if name.startswith("/"):
+ path[0] = path[0][1:]
+ subdir = succeed(RootDirectory(self.store))
+ else:
+ path.pop(0)
+ subdir = self.subdir(name)
+
+ if path:
+ return subdir.addCallback(lambda path: locate(path))
+ else:
+ return subdir
+
+ def subdir(self, name):
+ if not name:
+ return succeed(self)
+ if name == ".":
+ return succeed(self)
+ if name == "..":
+ return RootDirectory(self.store).locate(self.path[:-1])
+
+ raise NotFoundError("Directory %r has no subdirectory %r" % (str(self), name))
+
+ def list(self):
+ return ()
+
+
+class RootDirectory(Directory):
+ """
+ Root of virtual data hierarchy.
+ """
+ def __init__(self, store):
+ Directory.__init__(self, store, ())
+
+ self._children = {}
+
+ self._childClasses = {
+ "uids": UIDDirectory,
+ }
+
+ def subdir(self, name):
+ if name in self._children:
+ return succeed(self._children[name])
+
+ if name in self._childClasses:
+ self._children[name] = self._childClasses[name](self.store, self.path + (name,))
+ return succeed(self._children[name])
+
+ return Directory.subdir(self, name)
+
+ def list(self):
+ return ("%s/" % (n,) for n in self._childClasses)
+
+
+class UIDDirectory(Directory):
+ """
+ Directory containing all principals by UID.
+ """
+ def subdir(self, name):
+ txn = self.store.newTransaction()
+
+ def gotHome(home):
+ if home:
+ return HomeDirectory(self.store, self.path + (name,), home)
+
+ return Directory.subdir(self, name)
+
+ d = txn.calendarHomeWithUID(name)
+ d.addCallback(gotHome)
+ return d
+
+ def list(self):
+ for (txn, home) in self.store.eachCalendarHome():
+ yield home.uid()
+
+
+class HomeDirectory(Directory):
+ """
+ Home directory.
+ """
+ def __init__(self, store, path, home):
+ Directory.__init__(self, store, path)
+
+ self.home = home
+
+ def describe(self):
+ return succeed(
+ """Calendar home for UID: %(uid)s\n"""
+ """Quota: %(quotaUsed)s of %(quotaMax)s (%(quotaPercent).2s%%)\n"""
+ % {
+ "uid" : self.home.uid(),
+ "quotaUsed" : self.home.quotaUsed(),
+ "quotaMax" : self.home.quotaAllowedBytes(),
+ "quotaPercent" : self.home.quotaUsed() / self.home.quotaAllowedBytes(),
+ }
+ )
+
+
+def main(argv=sys.argv, stderr=sys.stderr, reactor=None):
+ """
+ Do the export.
+ """
+ if reactor is None:
+ from twisted.internet import reactor
+
+ options = ShellOptions()
+ try:
+ options.parseOptions(argv[1:])
+ except UsageError, e:
+ usage(e)
+
+ def makeService(store):
+ from twistedcaldav.config import config
+ return ShellService(store, options, reactor, config)
+
+ print "Initializing shell..."
+
+ utilityMain(options["config"], makeService, reactor)
Copied: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/tables.py (from rev 7695, CalendarServer/trunk/calendarserver/tools/tables.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/tables.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/tables.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,275 @@
+##
+# Copyright (c) 2009-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.
+##
+
+from sys import stdout
+import types
+
+class Table(object):
+ """
+ Class that allows pretty printing ascii tables.
+
+ The table supports multiline headers and footers, independent
+ column formatting by row, alternative tab-delimited output.
+ """
+
+ class ColumnFormat(object):
+ """
+ Defines the format string, justification and span for a column.
+ """
+
+ LEFT_JUSTIFY = 0
+ RIGHT_JUSTIFY = 1
+ CENTER_JUSTIFY = 2
+
+ def __init__(self, strFormat="%s", justify=LEFT_JUSTIFY, span=1):
+
+ self.format = strFormat
+ self.justify = justify
+ self.span = span
+
+ def __init__(self, table=None):
+
+ self.headers = []
+ self.headerColumnFormats = []
+ self.rows = []
+ self.footers = []
+ self.footerColumnFormats = []
+ self.columnCount = 0
+ self.defaultColumnFormats = []
+ self.columnFormatsByRow = {}
+
+ if table:
+ self.setData(table)
+
+ def setData(self, table):
+
+ self.hasTitles = True
+ self.headers.append(table[0])
+ self.rows = table[1:]
+ self._getMaxColumnCount()
+
+ def setDefaultColumnFormats(self, columnFormats):
+
+ self.defaultColumnFormats = columnFormats
+
+ def addDefaultColumnFormat(self, columnFormat):
+
+ self.defaultColumnFormats.append(columnFormat)
+
+ def setHeaders(self, rows, columnFormats=None):
+
+ self.headers = rows
+ self.headerColumnFormats = columnFormats if columnFormats else [None,] * len(self.headers)
+ self._getMaxColumnCount()
+
+ def addHeader(self, row, columnFormats=None):
+
+ self.headers.append(row)
+ self.headerColumnFormats.append(columnFormats)
+ self._getMaxColumnCount()
+
+ def addHeaderDivider(self, skipColumns=()):
+
+ self.headers.append((None, skipColumns,))
+ self.headerColumnFormats.append(None)
+
+ def setFooters(self, row, columnFormats=None):
+
+ self.footers = row
+ self.footerColumnFormats = columnFormats if columnFormats else [None,] * len(self.footers)
+ self._getMaxColumnCount()
+
+ def addFooter(self, row, columnFormats=None):
+
+ self.footers.append(row)
+ self.footerColumnFormats.append(columnFormats)
+ self._getMaxColumnCount()
+
+ def addRow(self, row=None, columnFormats=None):
+
+ self.rows.append(row)
+ if columnFormats:
+ self.columnFormatsByRow[len(self.rows) - 1] = columnFormats
+ self._getMaxColumnCount()
+
+ def addDivider(self, skipColumns=()):
+
+ self.rows.append((None, skipColumns,))
+
+ def printTable(self, os=stdout):
+
+ maxWidths = self._getMaxWidths()
+
+ self.printDivider(os, maxWidths, False)
+ if self.headers:
+ for header, format in zip(self.headers, self.headerColumnFormats):
+ self.printRow(os, header, self._getHeaderColumnFormat(format), maxWidths)
+ self.printDivider(os, maxWidths)
+ for ctr, row in enumerate(self.rows):
+ self.printRow(os, row, self._getColumnFormatForRow(ctr), maxWidths)
+ if self.footers:
+ self.printDivider(os, maxWidths, double=True)
+ for footer, format in zip(self.footers, self.footerColumnFormats):
+ self.printRow(os, footer, self._getFooterColumnFormat(format), maxWidths)
+ self.printDivider(os, maxWidths, False)
+
+ def printRow(self, os, row, format, maxWidths):
+
+ if row is None or type(row) is tuple and row[0] is None:
+ self.printDivider(os, maxWidths, skipColumns=row[1] if type(row) is tuple else ())
+ else:
+ if len(row) != len(maxWidths):
+ row = list(row)
+ row.extend([""] * (len(maxWidths) - len(row)))
+
+ t = "|"
+ ctr = 0
+ while ctr < len(row):
+ startCtr = ctr
+ maxWidth = 0
+ for _ignore_span in xrange(format[startCtr].span if format else 1):
+ maxWidth += maxWidths[ctr]
+ ctr += 1
+ maxWidth += 3 * ((format[startCtr].span - 1) if format else 0)
+ text = self._columnText(row, startCtr, format, width=maxWidth)
+ t += " " + text + " |"
+ t += "\n"
+ os.write(t)
+
+
+ def printDivider(self, os, maxWidths, intermediate=True, double=False, skipColumns=()):
+ t = "|" if intermediate else "+"
+ for widthctr, width in enumerate(maxWidths):
+ if widthctr in skipColumns:
+ c = " "
+ else:
+ c = "=" if double else "-"
+ t += c * (width + 2)
+ t += "+" if widthctr < len(maxWidths) - 1 else ("|" if intermediate else "+")
+ t += "\n"
+ os.write(t)
+
+ def printTabDelimitedData(self, os=stdout, footer=True):
+
+ if self.headers:
+ titles = [""] * len(self.headers[0])
+ for row, header in enumerate(self.headers):
+ for col, item in enumerate(header):
+ titles[col] += (" " if row and item else "") + item
+ self.printTabDelimitedRow(os, titles, self._getHeaderColumnFormat(self.headerColumnFormats[0]))
+ for ctr, row in enumerate(self.rows):
+ self.printTabDelimitedRow(os, row, self._getColumnFormatForRow(ctr))
+ if self.footers and footer:
+ for footer in self.footers:
+ self.printTabDelimitedRow(os, footer, self._getFooterColumnFormat(self.footerColumnFormats[0]))
+
+ def printTabDelimitedRow(self, os, row, format):
+
+ if row is None:
+ row = [""] * self.columnCount
+
+ if len(row) != self.columnCount:
+ row = list(row)
+ row.extend([""] * (self.columnCount - len(row)))
+
+ textItems = [self._columnText(row, ctr, format) for ctr in xrange((len(row)))]
+ os.write("\t".join(textItems) + "\n")
+
+ def _getMaxColumnCount(self):
+
+ self.columnCount = 0
+ if self.headers:
+ for header in self.headers:
+ self.columnCount = max(self.columnCount, len(header) if header else 0)
+ for row in self.rows:
+ self.columnCount = max(self.columnCount, len(row) if row else 0)
+ if self.footers:
+ for footer in self.footers:
+ self.columnCount = max(self.columnCount, len(footer) if footer else 0)
+
+ def _getMaxWidths(self):
+
+ maxWidths = [0] * self.columnCount
+
+ if self.headers:
+ for header, format in zip(self.headers, self.headerColumnFormats):
+ self._updateMaxWidthsFromRow(header, self._getHeaderColumnFormat(format), maxWidths)
+
+ for ctr, row in enumerate(self.rows):
+ self._updateMaxWidthsFromRow(row, self._getColumnFormatForRow(ctr), maxWidths)
+
+ if self.footers:
+ for footer, format in zip(self.footers, self.footerColumnFormats):
+ self._updateMaxWidthsFromRow(footer, self._getFooterColumnFormat(format), maxWidths)
+
+ return maxWidths
+
+ def _updateMaxWidthsFromRow(self, row, format, maxWidths):
+
+ if row and (type(row) is not tuple or row[0] is not None):
+ ctr = 0
+ while ctr < len(row):
+
+ text = self._columnText(row, ctr, format)
+ startCtr = ctr
+ for _ignore_span in xrange(format[startCtr].span if format else 1):
+ maxWidths[ctr] = max(maxWidths[ctr], len(text) / (format[startCtr].span if format else 1))
+ ctr += 1
+
+ def _getHeaderColumnFormat(self, format):
+
+ if format:
+ return format
+ else:
+ justify = Table.ColumnFormat.CENTER_JUSTIFY if len(self.headers) == 1 else Table.ColumnFormat.LEFT_JUSTIFY
+ return [Table.ColumnFormat(justify = justify)] * self.columnCount
+
+ def _getFooterColumnFormat(self, format):
+
+ if format:
+ return format
+ else:
+ return self.defaultColumnFormats
+
+ def _getColumnFormatForRow(self, ctr):
+
+ if ctr in self.columnFormatsByRow:
+ return self.columnFormatsByRow[ctr]
+ else:
+ return self.defaultColumnFormats
+
+ def _columnText(self, row, column, format, width=0):
+
+ if row is None or column >= len(row):
+ return ""
+
+ colData = row[column]
+ if colData is None:
+ colData = ""
+
+ columnFormat = format[column] if format and column < len(format) else Table.ColumnFormat()
+ if type(colData) in types.StringTypes:
+ text = colData
+ else:
+ text = columnFormat.format % colData
+ if width:
+ if columnFormat.justify == Table.ColumnFormat.LEFT_JUSTIFY:
+ text = text.ljust(width)
+ elif columnFormat.justify == Table.ColumnFormat.RIGHT_JUSTIFY:
+ text = text.rjust(width)
+ elif columnFormat.justify == Table.ColumnFormat.CENTER_JUSTIFY:
+ text = text.center(width)
+ return text
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_changeip.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_changeip.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_changeip.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -43,6 +43,17 @@
},
},
},
+ "Scheduling" : {
+ "iMIP" : {
+ "Receiving" : {
+ "Server" : "original_hostname",
+ },
+ "Sending" : {
+ "Server" : "original_hostname",
+ "Address" : "user at original_hostname",
+ },
+ },
+ },
}
updatePlist(plist, "10.1.1.1", "10.1.1.2", "original_hostname",
@@ -71,5 +82,16 @@
},
},
},
+ "Scheduling" : {
+ "iMIP" : {
+ "Receiving" : {
+ "Server" : "new_hostname",
+ },
+ "Sending" : {
+ "Server" : "new_hostname",
+ "Address" : "user at new_hostname",
+ },
+ },
+ },
}
)
Copied: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_export.py (from rev 7695, CalendarServer/trunk/calendarserver/tools/test/test_export.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_export.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_export.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,487 @@
+##
+# Copyright (c) 2011 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.
+##
+
+"""
+Unit tests for L{calendarsever.tools.export}.
+"""
+
+
+import sys
+from cStringIO import StringIO
+
+from twisted.trial.unittest import TestCase
+
+from twisted.internet.defer import inlineCallbacks
+from twisted.python.modules import getModule
+
+from twext.enterprise.ienterprise import AlreadyFinishedError
+
+from twistedcaldav.ical import Component
+from twistedcaldav.datafilters.test.test_peruserdata import dataForTwoUsers
+from twistedcaldav.datafilters.test.test_peruserdata import resultForUser2
+
+from calendarserver.tools import export
+from calendarserver.tools.export import ExportOptions, main
+from calendarserver.tools.export import DirectoryExporter, UIDExporter
+
+from twisted.python.filepath import FilePath
+from twistedcaldav.test.util import patchConfig
+from twisted.internet.defer import Deferred
+
+from txdav.common.datastore.test.util import buildStore
+from txdav.common.datastore.test.util import populateCalendarsFrom
+
+from calendarserver.tools.export import usage, exportToFile
+
+def holiday(uid):
+ return (
+ getModule("twistedcaldav.test").filePath
+ .sibling("data").child("Holidays").child(uid + ".ics")
+ .getContent()
+ )
+
+def sample(name):
+ return (
+ getModule("twistedcaldav.test").filePath
+ .sibling("data").child(name + ".ics").getContent()
+ )
+
+valentines = holiday("C31854DA-1ED0-11D9-A5E0-000A958A3252")
+newYears = holiday("C3184A66-1ED0-11D9-A5E0-000A958A3252")
+payday = sample("PayDay")
+
+one = sample("OneEvent")
+another = sample("AnotherEvent")
+third = sample("ThirdEvent")
+
+
+class CommandLine(TestCase):
+ """
+ Simple tests for command-line parsing.
+ """
+
+ def test_usageMessage(self):
+ """
+ The 'usage' message should print something to standard output (and
+ nothing to standard error) and exit.
+ """
+ orig = sys.stdout
+ orige = sys.stderr
+ try:
+ out = sys.stdout = StringIO()
+ err = sys.stderr = StringIO()
+ self.assertRaises(SystemExit, usage)
+ finally:
+ sys.stdout = orig
+ sys.stderr = orige
+ self.assertEquals(len(out.getvalue()) > 0, True, "No output.")
+ self.assertEquals(len(err.getvalue()), 0)
+
+
+ def test_oneRecord(self):
+ """
+ One '--record' option will result in a single L{DirectoryExporter}
+ object with no calendars in its list.
+ """
+ eo = ExportOptions()
+ eo.parseOptions(["--record", "users:bob"])
+ self.assertEquals(len(eo.exporters), 1)
+ exp = eo.exporters[0]
+ self.assertIsInstance(exp, DirectoryExporter)
+ self.assertEquals(exp.recordType, "users")
+ self.assertEquals(exp.shortName, "bob")
+ self.assertEquals(exp.collections, [])
+
+
+ def test_oneUID(self):
+ """
+ One '--uid' option will result in a single L{UIDExporter} object with no
+ calendars in its list.
+ """
+ eo = ExportOptions()
+ eo.parseOptions(["--uid", "bob's your guid"])
+ self.assertEquals(len(eo.exporters), 1)
+ exp = eo.exporters[0]
+ self.assertIsInstance(exp, UIDExporter)
+ self.assertEquals(exp.uid, "bob's your guid")
+
+
+ def test_homeAndCollections(self):
+ """
+ The --collection option adds specific calendars to the last home that
+ was exported.
+ """
+ eo = ExportOptions()
+ eo.parseOptions(["--record", "users:bob",
+ "--collection", "work stuff",
+ "--uid", "jethroUID",
+ "--collection=fun stuff"])
+ self.assertEquals(len(eo.exporters), 2)
+ exp = eo.exporters[0]
+ self.assertEquals(exp.recordType, "users")
+ self.assertEquals(exp.shortName, "bob")
+ self.assertEquals(exp.collections, ["work stuff"])
+ exp = eo.exporters[1]
+ self.assertEquals(exp.uid, "jethroUID")
+ self.assertEquals(exp.collections, ["fun stuff"])
+
+
+ def test_outputFileSelection(self):
+ """
+ The --output option selects the file to write to, '-' or no parameter
+ meaning stdout; the L{ExportOptions.openOutput} method returns that
+ file.
+ """
+ eo = ExportOptions()
+ eo.parseOptions([])
+ self.assertIdentical(eo.openOutput(), sys.stdout)
+ eo = ExportOptions()
+ eo.parseOptions(["--output", "-"])
+ self.assertIdentical(eo.openOutput(), sys.stdout)
+ eo = ExportOptions()
+ tmpnam = self.mktemp()
+ eo.parseOptions(["--output", tmpnam])
+ self.assertEquals(eo.openOutput().name, tmpnam)
+
+
+ def test_outputFileError(self):
+ """
+ If the output file cannot be opened for writing, an error will be
+ displayed to the user on stderr.
+ """
+ io = StringIO()
+ systemExit = self.assertRaises(
+ SystemExit, main, ['calendarserver_export',
+ '--output', '/not/a/file'], io
+ )
+ self.assertEquals(systemExit.code, 1)
+ self.assertEquals(
+ io.getvalue(),
+ "Unable to open output file for writing: "
+ "[Errno 2] No such file or directory: '/not/a/file'\n")
+
+
+
+class IntegrationTests(TestCase):
+ """
+ Tests for exporting data from a live store.
+ """
+
+ accountsFile = 'no-accounts.xml'
+ augmentsFile = 'no-augments.xml'
+
+ @inlineCallbacks
+ def setUp(self):
+ """
+ Set up a store and fix the imported C{utilityMain} function (normally
+ from L{calendarserver.tools.cmdline.utilityMain}) to point to a
+ temporary method of this class. Also, patch the imported C{reactor},
+ since the SUT needs to call C{reactor.stop()} in order to work with
+ L{utilityMain}.
+ """
+ self.mainCalled = False
+ self.patch(export, "utilityMain", self.fakeUtilityMain)
+
+
+ self.store = yield buildStore(self, None)
+ self.waitToStop = Deferred()
+
+
+ def stop(self):
+ """
+ Emulate reactor.stop(), which the service must call when it is done with
+ work.
+ """
+ self.waitToStop.callback(None)
+
+
+ def fakeUtilityMain(self, configFileName, serviceClass, reactor=None):
+ """
+ Verify a few basic things.
+ """
+ if self.mainCalled:
+ raise RuntimeError(
+ "Main called twice during this test; duplicate reactor run.")
+
+ patchConfig(
+ self,
+ DirectoryService=dict(
+ type="twistedcaldav.directory.xmlfile.XMLDirectoryService",
+ params=dict(
+ xmlFile=self.accountsFile
+ )
+ ),
+ ResourceService=dict(Enabled=False),
+ AugmentService=dict(
+ type="twistedcaldav.directory.augment.AugmentXMLDB",
+ params=dict(
+ xmlFiles=[self.augmentsFile]
+ )
+ )
+ )
+
+ self.mainCalled = True
+ self.usedConfigFile = configFileName
+ self.usedReactor = reactor
+ self.exportService = serviceClass(self.store)
+ self.exportService.startService()
+ self.addCleanup(self.exportService.stopService)
+
+
+ @inlineCallbacks
+ def test_serviceState(self):
+ """
+ export.main() invokes utilityMain with the configuration file specified
+ on the command line, and creates an L{ExporterService} pointed at the
+ appropriate store.
+ """
+ tempConfig = self.mktemp()
+ main(['calendarserver_export', '--config', tempConfig, '--output',
+ self.mktemp()], reactor=self)
+ self.assertEquals(self.mainCalled, True, "Main not called.")
+ self.assertEquals(self.usedConfigFile, tempConfig)
+ self.assertEquals(self.usedReactor, self)
+ self.assertEquals(self.exportService.store, self.store)
+ yield self.waitToStop
+
+
+ @inlineCallbacks
+ def test_emptyCalendar(self):
+ """
+ Exporting an empty calendar results in an empty calendar.
+ """
+ io = StringIO()
+ value = yield exportToFile([], io)
+ # it doesn't return anything, it writes to the file.
+ self.assertEquals(value, None)
+ # but it should write a valid component to the file.
+ self.assertEquals(Component.fromString(io.getvalue()),
+ Component.newCalendar())
+
+
+ def txn(self):
+ """
+ Create a new transaction and automatically clean it up when the test
+ completes.
+ """
+ aTransaction = self.store.newTransaction()
+ @inlineCallbacks
+ def maybeAbort():
+ try:
+ yield aTransaction.abort()
+ except AlreadyFinishedError:
+ pass
+ self.addCleanup(maybeAbort)
+ return aTransaction
+
+
+ @inlineCallbacks
+ def test_oneEventCalendar(self):
+ """
+ Exporting an calendar with one event in it will result in just that
+ event.
+ """
+ yield populateCalendarsFrom(
+ {
+ "home1": {
+ "calendar1": {
+ "valentines-day.ics": (valentines, {})
+ }
+ }
+ }, self.store
+ )
+
+ expected = Component.newCalendar()
+ [theComponent] = Component.fromString(valentines).subcomponents()
+ expected.addComponent(theComponent)
+
+ io = StringIO()
+ yield exportToFile(
+ [(yield self.txn().calendarHomeWithUID("home1"))
+ .calendarWithName("calendar1")], io
+ )
+ self.assertEquals(Component.fromString(io.getvalue()),
+ expected)
+
+
+ @inlineCallbacks
+ def test_twoSimpleEvents(self):
+ """
+ Exporting a calendar with two events in it will result in a VCALENDAR
+ component with both VEVENTs in it.
+ """
+ yield populateCalendarsFrom(
+ {
+ "home1": {
+ "calendar1": {
+ "valentines-day.ics": (valentines, {}),
+ "new-years-day.ics": (newYears, {})
+ }
+ }
+ }, self.store
+ )
+
+ expected = Component.newCalendar()
+ a = Component.fromString(valentines)
+ b = Component.fromString(newYears)
+ for comp in a, b:
+ for sub in comp.subcomponents():
+ expected.addComponent(sub)
+
+ io = StringIO()
+ yield exportToFile(
+ [(yield self.txn().calendarHomeWithUID("home1"))
+ .calendarWithName("calendar1")], io
+ )
+ self.assertEquals(Component.fromString(io.getvalue()),
+ expected)
+
+
+ @inlineCallbacks
+ def test_onlyOneVTIMEZONE(self):
+ """
+ C{VTIMEZONE} subcomponents with matching TZIDs in multiple event
+ calendar objects should only be rendered in the resulting output once.
+
+ (Note that the code to suppor this is actually in PyCalendar, not the
+ export tool itself.)
+ """
+ yield populateCalendarsFrom(
+ {
+ "home1": {
+ "calendar1": {
+ "1.ics": (one, {}), # EST
+ "2.ics": (another, {}), # EST
+ "3.ics": (third, {}) # PST
+ }
+ }
+ }, self.store
+ )
+
+ io = StringIO()
+ yield exportToFile(
+ [(yield self.txn().calendarHomeWithUID("home1"))
+ .calendarWithName("calendar1")], io
+ )
+ result = Component.fromString(io.getvalue())
+
+ def filtered(name):
+ for c in result.subcomponents():
+ if c.name() == name:
+ yield c
+
+ timezones = list(filtered("VTIMEZONE"))
+ events = list(filtered("VEVENT"))
+
+ # Sanity check to make sure we picked up all three events:
+ self.assertEquals(len(events), 3)
+
+ self.assertEquals(len(timezones), 2)
+ self.assertEquals(set([tz.propertyValue("TZID") for tz in timezones]),
+
+ # Use an intentionally wrong TZID in order to make
+ # sure we don't depend on caching effects elsewhere.
+ set(["America/New_Yrok", "US/Pacific"]))
+
+
+ @inlineCallbacks
+ def test_perUserFiltering(self):
+ """
+ L{exportToFile} performs per-user component filtering based on the owner
+ of that calendar.
+ """
+ yield populateCalendarsFrom(
+ {
+ "user02": {
+ "calendar1": {
+ "peruser.ics": (dataForTwoUsers, {}), # EST
+ }
+ }
+ }, self.store
+ )
+ io = StringIO()
+ yield exportToFile(
+ [(yield self.txn().calendarHomeWithUID("user02"))
+ .calendarWithName("calendar1")], io
+ )
+ self.assertEquals(
+ Component.fromString(resultForUser2),
+ Component.fromString(io.getvalue())
+ )
+
+
+ @inlineCallbacks
+ def test_full(self):
+ """
+ Running C{calendarserver_export} on the command line exports an ics
+ file. (Almost-full integration test, starting from the main point, using
+ as few test fakes as possible.)
+
+ Note: currently the only test for directory interaction.
+ """
+ yield populateCalendarsFrom(
+ {
+ "user02": {
+ # TODO: more direct test for skipping inbox
+ "inbox": {
+ "inbox-item.ics": (valentines, {})
+ },
+ "calendar1": {
+ "peruser.ics": (dataForTwoUsers, {}), # EST
+ }
+ }
+ }, self.store
+ )
+
+ augmentsData = """
+ <augments>
+ <record>
+ <uid>Default</uid>
+ <enable>true</enable>
+ <enable-calendar>true</enable-calendar>
+ <enable-addressbook>true</enable-addressbook>
+ </record>
+ </augments>
+ """
+ augments = FilePath(self.mktemp())
+ augments.setContent(augmentsData)
+
+ accountsData = """
+ <accounts realm="Test Realm">
+ <user>
+ <uid>user-under-test</uid>
+ <guid>user02</guid>
+ <name>Not Interesting</name>
+ <password>very-secret</password>
+ </user>
+ </accounts>
+ """
+ accounts = FilePath(self.mktemp())
+ accounts.setContent(accountsData)
+ output = FilePath(self.mktemp())
+ self.accountsFile = accounts.path
+ self.augmentsFile = augments.path
+ main(['calendarserver_export', '--output',
+ output.path, '--user', 'user-under-test'], reactor=self)
+
+ yield self.waitToStop
+
+ self.assertEquals(
+ Component.fromString(resultForUser2),
+ Component.fromString(output.getContent())
+ )
+
+
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_gateway.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_gateway.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -27,9 +27,7 @@
from twistedcaldav.test.util import TestCase, CapturingProcessProtocol
from calendarserver.tools.util import getDirectory
-from twistedcaldav.directory import augment
-
class GatewayTestCase(TestCase):
def setUp(self):
@@ -153,7 +151,8 @@
# This appears to be necessary in order for record.autoSchedule to
# reflect the change prior to the directory record expiration
- augment.AugmentService.refresh()
+ augmentService = directory.serviceForRecordType(directory.recordType_locations).augmentService
+ augmentService.refresh()
record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
self.assertEquals(record.fullName.decode("utf-8"), "Created Location 01 %s" % unichr(208))
@@ -187,7 +186,8 @@
# This appears to be necessary in order for record.autoSchedule to
# reflect the change
- augment.AugmentService.refresh()
+ augmentService = directory.serviceForRecordType(directory.recordType_locations).augmentService
+ augmentService.refresh()
record = directory.recordWithUID("836B1B66-2E9A-4F46-8B1C-3DD6772C20B2")
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_purge_old_events.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_purge_old_events.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_purge_old_events.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -26,7 +26,6 @@
from twisted.trial import unittest
from twistedcaldav.config import config
-from twistedcaldav.memcacher import Memcacher
from twistedcaldav.vcard import Component as VCardComponent
from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom, CommonCommonTests
@@ -348,9 +347,6 @@
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(Memcacher, "allowTestCache", True)
yield super(PurgeOldEventsTests, self).setUp()
self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
yield self.populate()
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_resources.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_resources.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/test/test_resources.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -16,7 +16,6 @@
from calendarserver.tools.resources import migrateResources
from twisted.internet.defer import inlineCallbacks, succeed
-from twistedcaldav.directory import augment
from twistedcaldav.directory.directory import DirectoryService
from twistedcaldav.test.util import TestCase
@@ -41,8 +40,9 @@
class StubDirectoryService(object):
- def __init__(self):
+ def __init__(self, augmentService):
self.records = {}
+ self.augmentService = augmentService
def recordWithGUID(self, guid):
return None
@@ -117,8 +117,7 @@
def queryMethod(sourceService, recordType, verbose=False):
return data[recordType]
- self.patch(augment, "AugmentService", StubAugmentService)
- directoryService = StubDirectoryService()
+ directoryService = StubDirectoryService(StubAugmentService())
yield migrateResources(None, directoryService, queryMethod=queryMethod)
for guid, recordType in (
('6C99E240-E915-4012-82FA-99E0F638D7EF', DirectoryService.recordType_resources),
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2011 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.
@@ -14,6 +14,10 @@
# limitations under the License.
##
+"""
+Utility functionality shared between calendarserver tools.
+"""
+
__all__ = [
"loadConfig",
"getDirectory",
@@ -34,9 +38,10 @@
from calendarserver.provision.root import RootResource
+
from twistedcaldav import memcachepool
from twistedcaldav.config import config, ConfigurationError
-from twistedcaldav.directory import augment, calendaruserproxy
+from twistedcaldav.directory import calendaruserproxy
from twistedcaldav.directory.aggregate import AggregateDirectoryService
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
from twistedcaldav.notify import NotifierFactory
@@ -124,13 +129,14 @@
# Load augment/proxy db classes now
augmentClass = namedClass(config.AugmentService.type)
- augment.AugmentService = augmentClass(**config.AugmentService.params)
+ augmentService = augmentClass(**config.AugmentService.params)
proxydbClass = namedClass(config.ProxyDBService.type)
calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
# Wait for directory service to become available
BaseDirectoryService = namedClass(config.DirectoryService.type)
+ config.DirectoryService.params.augmentService = augmentService
directory = BaseDirectoryService(config.DirectoryService.params)
while not directory.isAvailable():
sleep(5)
@@ -140,6 +146,7 @@
if config.ResourceService.Enabled:
resourceClass = namedClass(config.ResourceService.type)
+ config.ResourceService.params.augmentService = augmentService
resourceDirectory = resourceClass(config.ResourceService.params)
resourceDirectory.realmName = directory.realmName
directories.append(resourceDirectory)
@@ -270,3 +277,5 @@
% (description, dirpath)
)
+
+
Modified: CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/warmup.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/warmup.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/calendarserver/tools/warmup.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,7 +1,7 @@
#!/usr/bin/env python
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -168,7 +168,7 @@
child = calendarCollection.getChild(name)
#sys.stdout.write("+")
- child.iCalendarText()
+ child._text()
readProperties(child)
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/webadmin
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/calendarserver/webcal
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-apple.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-apple.plist 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-apple.plist 2011-06-30 20:44:13 UTC (rev 7697)
@@ -522,6 +522,8 @@
<!-- Directory searching -->
<key>DirectoryAddressBook</key>
<dict>
+ <key>Enabled</key>
+ <true/>
<key>params</key>
<dict>
<key>queryUserRecords</key>
Modified: CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-test.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-test.plist 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/conf/caldavd-test.plist 2011-06-30 20:44:13 UTC (rev 7697)
@@ -259,6 +259,10 @@
<string></string>
<key>recordName</key>
<string>uid</string>
+ <key>loginEnabledAttr</key>
+ <string>loginEnabled</string>
+ <key>loginEnabledValue</key>
+ <string>yes</string>
</dict>
<key>groups</key>
<dict>
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/certupdate/calendarcertupdate.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/certupdate/calendarcertupdate.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/certupdate/calendarcertupdate.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -18,11 +18,10 @@
# Apple's written consent.
import datetime
-import os
import subprocess
import sys
-from plistlib import readPlist, readPlistFromString, writePlist
+from plistlib import readPlist, writePlist
LOG = "/var/log/caldavd/certupdate.log"
SERVICE_NAME = "calendar"
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/migration/calendarmigrator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/migration/calendarmigrator.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/migration/calendarmigrator.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -305,8 +305,9 @@
"""
if enableCalDAV or enableCardDAV:
- log("Starting service via serveradmin")
- ret = subprocess.call([SERVER_ADMIN, "start", "calendar"])
+ serviceName = "calendar" if enableCalDAV else "addressbook"
+ log("Starting service via serveradmin start %s" % (serviceName,))
+ ret = subprocess.call([SERVER_ADMIN, "start", serviceName])
log("serveradmin exited with %d" % (ret,))
@@ -444,16 +445,28 @@
if key in caldav:
combined[key] = caldav[key]
- # "Wiki" is a new authentication in v2.x; copy all "Authentication" sub-keys # over, and "Wiki" will be picked up from the new plist:
+ # Copy all "Authentication" sub-keys
if "Authentication" in caldav:
+ if "Authentication" not in combined:
+ combined["Authentication"] = { }
for key in caldav["Authentication"]:
combined["Authentication"][key] = caldav["Authentication"][key]
+ # Examine the Wiki URL -- if it's using :8089 then we leave the Wiki
+ # section as is. Otherwise, reset it so that it picks up the coded
+ # default
+ if "Wiki" in combined["Authentication"]:
+ if "URL" in combined["Authentication"]["Wiki"]:
+ url = combined["Authentication"]["Wiki"]["URL"]
+ if ":8089" not in url:
+ combined["Authentication"]["Wiki"] = { "Enabled" : True }
+
+
# Strip out any unknown params from the DirectoryService:
if "DirectoryService" in caldav:
combined["DirectoryService"] = caldav["DirectoryService"]
for key in combined["DirectoryService"]["params"].keys():
- if key not in ("node", "cacheTimeout", "xmlFile"):
+ if key in ("requireComputerRecord",):
del combined["DirectoryService"]["params"][key]
# Merge ports
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/migration/test/test_migrator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/migration/test/test_migrator.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/migration/test/test_migrator.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -20,6 +20,28 @@
)
import contrib.migration.calendarmigrator
+class FakeUser(object):
+ pw_uid = 6543
+
+
+class FakeGroup(object):
+ gr_gid = 7654
+
+
+class FakePwd(object):
+ def getpwnam(self, nam):
+ if nam != 'calendar':
+ raise RuntimeError("Only 'calendar' user supported for testing.")
+ return FakeUser()
+
+
+class FakeGrp(object):
+ def getgrnam(self, nam):
+ if nam != 'calendar':
+ raise RuntimeError("Only 'calendar' group supported for testing.")
+ return FakeGroup()
+
+
class MigrationTests(twistedcaldav.test.util.TestCase):
"""
Calendar Server Migration Tests
@@ -27,7 +49,10 @@
def setUp(self):
# Disable logging during tests
+
self.patch(contrib.migration.calendarmigrator, "log", lambda _: None)
+ self.patch(contrib.migration.calendarmigrator, "pwd", FakePwd())
+ self.patch(contrib.migration.calendarmigrator, "grp", FakeGrp())
def test_mergeSSL(self):
@@ -293,6 +318,125 @@
self.assertEquals(newCombined, expected)
+ def test_mergeDirectoryService(self):
+
+ # Ensure caldavd DirectoryService config is carried over, except
+ # for requireComputerRecord which was is obsolete.
+
+ oldCalDAV = {
+ "DirectoryService": {
+ "type" : "twistedcaldav.directory.appleopendirectory.OpenDirectoryService",
+ "params" : {
+ "node" : "/Search",
+ "cacheTimeout" : 15,
+ "restrictToGroup" : "test-group",
+ "restrictEnabledRecords" : True,
+ "negativeCaching" : False,
+ "requireComputerRecord" : True,
+ },
+ },
+ }
+ oldCardDAV = { "Is this ignored?" : True }
+ expected = {
+ "DirectoryService": {
+ "type" : "twistedcaldav.directory.appleopendirectory.OpenDirectoryService",
+ "params" : {
+ "node" : "/Search",
+ "cacheTimeout" : 15,
+ "restrictToGroup" : "test-group",
+ "restrictEnabledRecords" : True,
+ "negativeCaching" : False,
+ },
+ },
+ "BindHTTPPorts": [8008, 8800],
+ "BindSSLPorts": [8443, 8843],
+ "EnableSSL" : False,
+ "HTTPPort": 8008,
+ "RedirectHTTPToHTTPS": False,
+ "SSLAuthorityChain": "",
+ "SSLCertificate": "",
+ "SSLPort": 8443,
+ "SSLPrivateKey": "",
+ }
+ newCombined = { }
+ mergePlist(oldCalDAV, oldCardDAV, newCombined)
+ self.assertEquals(newCombined, expected)
+
+
+ def test_mergeAuthentication(self):
+
+ # Ensure caldavd Authentication config for wiki gets reset because
+ # the port number has changed for the wiki rpc url
+
+ oldCalDAV = {
+ "Authentication": {
+ "Wiki" : {
+ "UseSSL" : False,
+ "Enabled" : True,
+ "Hostname" : "127.0.0.1",
+ "URL" : "http://127.0.0.1/RPC2",
+ },
+ },
+ }
+ oldCardDAV = { "Is this ignored?" : True }
+ expected = {
+ "Authentication": {
+ "Wiki" : {
+ "Enabled" : True,
+ },
+ },
+ "BindHTTPPorts": [8008, 8800],
+ "BindSSLPorts": [8443, 8843],
+ "EnableSSL" : False,
+ "HTTPPort": 8008,
+ "RedirectHTTPToHTTPS": False,
+ "SSLAuthorityChain": "",
+ "SSLCertificate": "",
+ "SSLPort": 8443,
+ "SSLPrivateKey": "",
+ }
+ newCombined = { }
+ mergePlist(oldCalDAV, oldCardDAV, newCombined)
+ self.assertEquals(newCombined, expected)
+
+ # If the port is :8089, leave the wiki config as is, since it's
+ # already set for Lio
+
+ oldCalDAV = {
+ "Authentication": {
+ "Wiki" : {
+ "UseSSL" : False,
+ "Enabled" : True,
+ "Hostname" : "127.0.0.1",
+ "URL" : "http://127.0.0.1:8089/RPC2",
+ },
+ },
+ }
+ oldCardDAV = { "Is this ignored?" : True }
+ expected = {
+ "Authentication": {
+ "Wiki" : {
+ "UseSSL" : False,
+ "Enabled" : True,
+ "Hostname" : "127.0.0.1",
+ "URL" : "http://127.0.0.1:8089/RPC2",
+ },
+ },
+ "BindHTTPPorts": [8008, 8800],
+ "BindSSLPorts": [8443, 8843],
+ "EnableSSL" : False,
+ "HTTPPort": 8008,
+ "RedirectHTTPToHTTPS": False,
+ "SSLAuthorityChain": "",
+ "SSLCertificate": "",
+ "SSLPort": 8443,
+ "SSLPrivateKey": "",
+ }
+ newCombined = { }
+ mergePlist(oldCalDAV, oldCardDAV, newCombined)
+ self.assertEquals(newCombined, expected)
+
+
def test_examinePreviousSystem(self):
"""
Set up a virtual system in various configurations, then ensure the
@@ -361,7 +505,7 @@
"/Library/CalendarServer/Documents", # Old Cal DocRoot value
"/Library/CalendarServer/Data", # Old Cal DataRoot value
"/Library/AddressBookServer/Documents", # Old AB DocRoot value
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -420,7 +564,7 @@
"/Library/CalendarServer/Documents", # Old Cal DocRoot value
"/Library/CalendarServer/Data", # Old Cal DataRoot value
"/Library/AddressBookServer/Documents", # Old AB DocRoot value
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -479,7 +623,7 @@
"/NonStandard/CalendarServer/Documents", # Old Cal DocRoot Value
"/NonStandard/CalendarServer/Data", # Old Cal DataRoot Value
"/NonStandard/AddressBookServer/Documents", # Old AB DocRoot Value
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -538,7 +682,7 @@
"/Volumes/External/CalendarServer/Documents", # Old Cal DocRoot Value
"/Volumes/External/CalendarServer/Data", # Old Cal DataRoot Value
"/Library/AddressBookServer/Documents", # Old AB DocRoot Value
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -582,7 +726,7 @@
None, # Old Cal DocRoot value
None, # Old Cal DataRoot value
"/Library/AddressBookServer/Documents", # Old AB DocRoot value
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -630,7 +774,7 @@
"Documents", # Old Cal DocRoot value
"Data", # Old Cal DataRoot value
None, # Old AB Docs
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -678,7 +822,7 @@
"/Volumes/External/Calendar/Documents", # Old Cal DocRoot value
"/Volumes/External/Calendar/Data", # Old Cal DataRoot value
None, # Old AB Docs
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -726,7 +870,7 @@
"Documents", # Old Cal DocRoot value
"Data", # Old Cal DocRoot value
None, # Old AB Docs
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -777,7 +921,7 @@
"/Volumes/External/CalendarDocuments/", # Old Cal DocRoot value
"/CalendarData", # Old Cal DocRoot value
None, # Old AB Docs
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
)
),
@@ -868,7 +1012,7 @@
"/Library/CalendarServer/Documents", # oldCalDocumentRootValue
"/Library/CalendarServer/Data", # oldCalDataRootValue
"/Library/AddressBookServer/Documents", # oldABDocumentRootValue
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
),
( # expected return values
"/Volumes/new/Library/Server/Calendar and Contacts",
@@ -878,11 +1022,11 @@
),
[ # expected DiskAccessor history
('ditto', '/Volumes/old/Library/CalendarServer/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', FakeUser.pw_uid, FakeGroup.gr_gid),
('ditto', '/Volumes/old/Library/CalendarServer/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', FakeUser.pw_uid, FakeGroup.gr_gid),
('ditto', '/Volumes/old/Library/AddressBookServer/Documents/addressbooks', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', FakeUser.pw_uid, FakeGroup.gr_gid),
]
),
@@ -943,7 +1087,7 @@
"/NonStandard/CalendarServer/Documents", # oldCalDocumentRootValue
"/NonStandard/CalendarServer/Data", # oldCalDataRootValue
"/NonStandard/AddressBookServer/Documents", # oldABDocumentRootValue
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
),
( # expected return values
"/Volumes/new/Library/Server/Calendar and Contacts",
@@ -953,11 +1097,11 @@
),
[
('ditto', '/Volumes/old/NonStandard/CalendarServer/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', FakeUser.pw_uid, FakeGroup.gr_gid),
('ditto', '/Volumes/old/NonStandard/CalendarServer/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', FakeUser.pw_uid, FakeGroup.gr_gid),
('ditto', '/Volumes/old/NonStandard/AddressBookServer/Documents/addressbooks', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents/addressbooks', FakeUser.pw_uid, FakeGroup.gr_gid),
]
),
@@ -1018,7 +1162,7 @@
"/Volumes/External/CalendarServer/Documents", # oldCalDocumentRootValue
"/Volumes/External/CalendarServer/Data", # oldCalDataRootValue
"/Library/AddressBookServer/Documents", # oldABDocumentRootValue
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
),
( # expected return values
"/Volumes/new/Library/Server/Calendar and Contacts",
@@ -1028,7 +1172,7 @@
),
[
('ditto', '/Volumes/old/Library/AddressBookServer/Documents/addressbooks', '/Volumes/External/CalendarServer/Documents/addressbooks'),
- ('chown-recursive', '/Volumes/External/CalendarServer/Documents/addressbooks', 93, 93),
+ ('chown-recursive', '/Volumes/External/CalendarServer/Documents/addressbooks', FakeUser.pw_uid, FakeGroup.gr_gid),
]
),
@@ -1079,7 +1223,7 @@
"Documents", # oldCalDocumentRootValue
"Data", # oldCalDataRootValue
None, # oldABDocumentRootValue
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
),
( # expected return values
"/Volumes/new/Library/Server/Calendar and Contacts",
@@ -1089,9 +1233,9 @@
),
[
('ditto', '/Volumes/old/Library/Server/Calendar and Contacts/Documents', '/Volumes/new/Library/Server/Calendar and Contacts/Documents'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Documents', FakeUser.pw_uid, FakeGroup.gr_gid),
('ditto', '/Volumes/old/Library/Server/Calendar and Contacts/Data', '/Volumes/new/Library/Server/Calendar and Contacts/Data'),
- ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', 93, 93),
+ ('chown-recursive', '/Volumes/new/Library/Server/Calendar and Contacts/Data', FakeUser.pw_uid, FakeGroup.gr_gid),
]
),
@@ -1142,7 +1286,7 @@
"Documents", # oldCalDocumentRootValue
"Data", # oldCalDataRootValue
None, # oldABDocumentRootValue
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
),
( # expected return values
"/Volumes/External/Library/Server/Calendar and Contacts",
@@ -1203,7 +1347,7 @@
"/Volumes/External/CalendarDocuments/", # oldCalDocumentRootValue
"/CalendarData", # oldCalDataRootValue
None, # oldABDocumentRootValue
- 93, 93, # user id, group id
+ FakeUser.pw_uid, FakeGroup.gr_gid, # user id, group id
),
( # expected return values
"/Volumes/External/Library/Server/Calendar and Contacts",
@@ -1213,7 +1357,7 @@
),
[
('ditto', '/Volumes/old/CalendarData', '/Volumes/External/Library/Server/Calendar and Contacts/Data'),
- ('chown-recursive', '/Volumes/External/Library/Server/Calendar and Contacts/Data', 93, 93),
+ ('chown-recursive', '/Volumes/External/Library/Server/Calendar and Contacts/Data', FakeUser.pw_uid, FakeGroup.gr_gid),
]
),
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/benchmark.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/benchmark.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/benchmark.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -24,7 +24,6 @@
from twisted.python.filepath import FilePath
from twisted.python.usage import UsageError, Options, portCoerce
-from twisted.python.reflect import namedAny
from twisted.internet.protocol import ProcessProtocol
from twisted.protocols.basic import LineReceiver
from twisted.internet.defer import (
Copied: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.csv (from rev 7695, CalendarServer/trunk/contrib/performance/loadtest/accounts.csv)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.csv (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.csv 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,99 @@
+user01,user01,User 01,user01 at example.com
+user02,user02,User 02,user02 at example.com
+user03,user03,User 03,user03 at example.com
+user04,user04,User 04,user04 at example.com
+user05,user05,User 05,user05 at example.com
+user06,user06,User 06,user06 at example.com
+user07,user07,User 07,user07 at example.com
+user08,user08,User 08,user08 at example.com
+user09,user09,User 09,user09 at example.com
+user10,user10,User 10,user10 at example.com
+user11,user11,User 11,user11 at example.com
+user12,user12,User 12,user12 at example.com
+user13,user13,User 13,user13 at example.com
+user14,user14,User 14,user14 at example.com
+user15,user15,User 15,user15 at example.com
+user16,user16,User 16,user16 at example.com
+user17,user17,User 17,user17 at example.com
+user18,user18,User 18,user18 at example.com
+user19,user19,User 19,user19 at example.com
+user20,user20,User 20,user20 at example.com
+user21,user21,User 21,user21 at example.com
+user22,user22,User 22,user22 at example.com
+user23,user23,User 23,user23 at example.com
+user24,user24,User 24,user24 at example.com
+user25,user25,User 25,user25 at example.com
+user26,user26,User 26,user26 at example.com
+user27,user27,User 27,user27 at example.com
+user28,user28,User 28,user28 at example.com
+user29,user29,User 29,user29 at example.com
+user30,user30,User 30,user30 at example.com
+user31,user31,User 31,user31 at example.com
+user32,user32,User 32,user32 at example.com
+user33,user33,User 33,user33 at example.com
+user34,user34,User 34,user34 at example.com
+user35,user35,User 35,user35 at example.com
+user36,user36,User 36,user36 at example.com
+user37,user37,User 37,user37 at example.com
+user38,user38,User 38,user38 at example.com
+user39,user39,User 39,user39 at example.com
+user40,user40,User 40,user40 at example.com
+user41,user41,User 41,user41 at example.com
+user42,user42,User 42,user42 at example.com
+user43,user43,User 43,user43 at example.com
+user44,user44,User 44,user44 at example.com
+user45,user45,User 45,user45 at example.com
+user46,user46,User 46,user46 at example.com
+user47,user47,User 47,user47 at example.com
+user48,user48,User 48,user48 at example.com
+user49,user49,User 49,user49 at example.com
+user50,user50,User 50,user50 at example.com
+user51,user51,User 51,user51 at example.com
+user52,user52,User 52,user52 at example.com
+user53,user53,User 53,user53 at example.com
+user54,user54,User 54,user54 at example.com
+user55,user55,User 55,user55 at example.com
+user56,user56,User 56,user56 at example.com
+user57,user57,User 57,user57 at example.com
+user58,user58,User 58,user58 at example.com
+user59,user59,User 59,user59 at example.com
+user60,user60,User 60,user60 at example.com
+user61,user61,User 61,user61 at example.com
+user62,user62,User 62,user62 at example.com
+user63,user63,User 63,user63 at example.com
+user64,user64,User 64,user64 at example.com
+user65,user65,User 65,user65 at example.com
+user66,user66,User 66,user66 at example.com
+user67,user67,User 67,user67 at example.com
+user68,user68,User 68,user68 at example.com
+user69,user69,User 69,user69 at example.com
+user70,user70,User 70,user70 at example.com
+user71,user71,User 71,user71 at example.com
+user72,user72,User 72,user72 at example.com
+user73,user73,User 73,user73 at example.com
+user74,user74,User 74,user74 at example.com
+user75,user75,User 75,user75 at example.com
+user76,user76,User 76,user76 at example.com
+user77,user77,User 77,user77 at example.com
+user78,user78,User 78,user78 at example.com
+user79,user79,User 79,user79 at example.com
+user80,user80,User 80,user80 at example.com
+user81,user81,User 81,user81 at example.com
+user82,user82,User 82,user82 at example.com
+user83,user83,User 83,user83 at example.com
+user84,user84,User 84,user84 at example.com
+user85,user85,User 85,user85 at example.com
+user86,user86,User 86,user86 at example.com
+user87,user87,User 87,user87 at example.com
+user88,user88,User 88,user88 at example.com
+user89,user89,User 89,user89 at example.com
+user90,user90,User 90,user90 at example.com
+user91,user91,User 91,user91 at example.com
+user92,user92,User 92,user92 at example.com
+user93,user93,User 93,user93 at example.com
+user94,user94,User 94,user94 at example.com
+user95,user95,User 95,user95 at example.com
+user96,user96,User 96,user96 at example.com
+user97,user97,User 97,user97 at example.com
+user98,user98,User 98,user98 at example.com
+user99,user99,User 99,user99 at example.com
Deleted: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.txt
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.txt 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/accounts.txt 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,99 +0,0 @@
-user01 user01
-user02 user02
-user03 user03
-user04 user04
-user05 user05
-user06 user06
-user07 user07
-user08 user08
-user09 user09
-user10 user10
-user11 user11
-user12 user12
-user13 user13
-user14 user14
-user15 user15
-user16 user16
-user17 user17
-user18 user18
-user19 user19
-user20 user20
-user21 user21
-user22 user22
-user23 user23
-user24 user24
-user25 user25
-user26 user26
-user27 user27
-user28 user28
-user29 user29
-user30 user30
-user31 user31
-user32 user32
-user33 user33
-user34 user34
-user35 user35
-user36 user36
-user37 user37
-user38 user38
-user39 user39
-user40 user40
-user41 user41
-user42 user42
-user43 user43
-user44 user44
-user45 user45
-user46 user46
-user47 user47
-user48 user48
-user49 user49
-user50 user50
-user51 user51
-user52 user52
-user53 user53
-user54 user54
-user55 user55
-user56 user56
-user57 user57
-user58 user58
-user59 user59
-user60 user60
-user61 user61
-user62 user62
-user63 user63
-user64 user64
-user65 user65
-user66 user66
-user67 user67
-user68 user68
-user69 user69
-user70 user70
-user71 user71
-user72 user72
-user73 user73
-user74 user74
-user75 user75
-user76 user76
-user77 user77
-user78 user78
-user79 user79
-user80 user80
-user81 user81
-user82 user82
-user83 user83
-user84 user84
-user85 user85
-user86 user86
-user87 user87
-user88 user88
-user89 user89
-user90 user90
-user91 user91
-user92 user92
-user93 user93
-user94 user94
-user95 user95
-user96 user96
-user97 user97
-user98 user98
-user99 user99
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/config.plist
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/config.plist 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/config.plist 2011-06-30 20:44:13 UTC (rev 7697)
@@ -19,65 +19,243 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
-
+ <!-- Identify the server to be load tested. -->
<key>server</key>
- <dict>
- <key>host</key>
- <string>127.0.0.1</string>
+ <string>https://127.0.0.1:8443/</string>
- <key>port</key>
- <integer>8008</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>
<dict>
+ <!-- The loader is the fully-qualified Python name of a callable which
+ returns a list of directory service records defining all of the
+ client accounts to use.
+
+ loadtest.sim.recordsFromCSVFile reads username, password, mailto
+ triples from a CSV file and returns them as a list of faked
+ directory service records. -->
<key>loader</key>
- <string>loadtest.sim.recordsFromTextFile</string>
+ <string>loadtest.sim.recordsFromCSVFile</string>
+
+ <!-- Keyword arguments may be passed to the loader. -->
<key>params</key>
<dict>
<key>path</key>
- <string>loadtest/accounts.txt</string>
+ <string>loadtest/accounts.csv</string>
</dict>
</dict>
+ <!-- Define how many clients will participate in the load test and how they
+ will show up. -->
<key>arrival</key>
<dict>
+
+ <!-- Specify a class which creates new clients and introduces them into the test.
+
+ loadtest.population.SmoothRampUp introduces groups of new clients at
+ fixed intervals up to a maximum. The size of the group, interval,
+ and maximum are configured by the parameters below. The total
+ number of clients is groups * groupSize, which needs to be no larger
+ than the number of credentials created in the accounts section. -->
<key>factory</key>
<string>loadtest.population.SmoothRampUp</string>
- <key>groups</key>
- <integer>60</integer>
+ <key>params</key>
+ <dict>
+ <!-- groups gives the total number of groups of clients to introduce.-->
+ <key>groups</key>
+ <integer>60</integer>
- <key>groupSize</key>
- <integer>1</integer>
+ <!-- groupSize is the number of clients in each group of clients. It's
+ really only a "smooth" ramp up if this is pretty small. -->
+ <key>groupSize</key>
+ <integer>1</integer>
- <key>interval</key>
- <integer>13</integer>
+ <!-- Number of seconds between the introduction of each group. -->
+ <key>interval</key>
+ <integer>13</integer>
+ </dict>
</dict>
+ <!-- Define the kinds of software and user behavior the load simulation
+ will simulate. -->
<key>clients</key>
+
+ <!-- Have as many different kinds of software and user behavior
+ configurations as you want. Each is a dict -->
<array>
+
<dict>
+
+ <!-- Here is a Snow Leopard iCal simulator. -->
<key>software</key>
<string>loadtest.ical.SnowLeopard</string>
+ <!-- The profiles define certain types of user behavior on top of the
+ client software being simulated. -->
<key>profiles</key>
<array>
- <string>loadtest.profiles.Eventer</string>
- <string>loadtest.profiles.Inviter</string>
- <string>loadtest.profiles.Accepter</string>
+
+ <!-- First an event-creating profile, which will periodically create
+ new events at a random time on a random calendar. -->
+ <dict>
+ <key>class</key>
+ <string>loadtest.profiles.Eventer</string>
+
+ <key>params</key>
+ <dict>
+ <!-- Define the interval (in seconds) at which this profile will
+ use its client to create a new event. -->
+ <key>interval</key>
+ <integer>60</integer>
+
+ <!-- Define how start times (DTSTART) for the randomly generated
+ events will be selected. This is an example of a
+ "Distribution" parameter. The value for most "Distribution"
+ parameters are interchangeable and extensible. -->
+ <key>eventStartDistribution</key>
+ <dict>
+
+ <!-- This distribution is pretty specialized. It produces
+ timestamps in the near future, limited to certain days of
+ the week and certain hours of the day. -->
+ <key>type</key>
+ <string>stats.WorkDistribution</string>
+
+ <key>params</key>
+ <dict>
+ <!-- These are the days of the week the distribution will
+ use. -->
+ <key>daysOfWeek</key>
+ <array>
+ <string>mon</string>
+ <string>tue</string>
+ <string>wed</string>
+ <string>thu</string>
+ <string>fri</string>
+ </array>
+
+ <!-- The earliest hour of a day at which an event might be
+ scheduled. -->
+ <key>beginHour</key>
+ <integer>8</integer>
+
+ <!-- And the latest hour of a day (at which an event will be
+ scheduled to begin!). -->
+ <key>endHour</key>
+ <integer>16</integer>
+
+ <!-- The timezone in which the event is scheduled.
+ (XXX Does this really work right?) -->
+ <key>tzname</key>
+ <string>America/Los_Angeles</string>
+ </dict>
+ </dict>
+ </dict>
+ </dict>
+
+ <!-- This profile invites new attendees to existing events. -->
+ <dict>
+ <key>class</key>
+ <string>loadtest.profiles.Inviter</string>
+
+ <key>params</key>
+ <dict>
+ <!-- Define the frequency at which new invitations will be sent
+ out. -->
+ <key>sendInvitationDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>stats.NormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mu gives the mean of the normal distribution (in
+ seconds). -->
+ <key>mu</key>
+ <integer>60</integer>
+
+ <!-- and sigma gives its standard deviation. -->
+ <key>sigma</key>
+ <integer>5</integer>
+ </dict>
+ </dict>
+
+ <!-- Define the distribution of who will be invited to an event.
+ Each set of credentials loaded by the load tester has an
+ index; samples from this distribution will be added to that
+ index to arrive at the index of some other credentials,
+ which will be the target of the invitation. -->
+ <key>inviteeDistanceDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>stats.UniformIntegerDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- The minimum value (inclusive) of the uniform
+ distribution. -->
+ <key>min</key>
+ <integer>-100</integer>
+ <!-- The maximum value (exclusive) of the uniform
+ distribution. -->
+ <key>max</key>
+ <integer>101</integer>
+ </dict>
+ </dict>
+ </dict>
+
+ </dict>
+
+ <!-- This profile accepts invitations to events. -->
+ <dict>
+ <key>class</key>
+ <string>loadtest.profiles.Accepter</string>
+
+ <key>params</key>
+ <dict>
+ <!-- Define how long to wait after seeing a new invitation before
+ accepting it. -->
+ <key>acceptDelayDistribution</key>
+ <dict>
+ <key>type</key>
+ <string>stats.NormalDistribution</string>
+ <key>params</key>
+ <dict>
+ <!-- mean -->
+ <key>mu</key>
+ <integer>360</integer>
+ <!-- standard deviation -->
+ <key>sigma</key>
+ <integer>60</integer>
+ </dict>
+ </dict>
+ </dict>
+
+ </dict>
</array>
+ <!-- Determine the frequency at which this client configuration will
+ appear in the clients which are created by the load tester. -->
<key>weight</key>
<integer>1</integer>
</dict>
</array>
+ <!-- Define some log observers to report on the load test. -->
<key>observers</key>
<array>
+ <!-- ReportStatistics generates an end-of-run summary of the HTTP
+ requests made, their timings, and their results. -->
<string>loadtest.population.ReportStatistics</string>
+
+ <!-- RequestLogger generates a realtime log of all HTTP requests made
+ during the load test. -->
<string>loadtest.ical.RequestLogger</string>
+
+ <!-- OperationLogger generates an end-of-run summary of the gross
+ operations performed (logical operations which may span more than
+ one HTTP request, such as inviting an attendee to an event). -->
<string>loadtest.profiles.OperationLogger</string>
</array>
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/ical.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/ical.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -16,9 +16,7 @@
##
from uuid import uuid4
-from operator import getitem
-from pprint import pformat
-from datetime import datetime
+from datetime import timedelta, datetime
from urlparse import urlparse, urlunparse
from xml.etree import ElementTree
@@ -31,7 +29,7 @@
from twisted.python.log import addObserver, err, msg
from twisted.python.filepath import FilePath
from twisted.python.failure import Failure
-from twisted.internet.defer import Deferred, inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.task import LoopingCall
from twisted.web.http_headers import Headers
from twisted.web.http import OK, MULTI_STATUS, CREATED, NO_CONTENT
@@ -143,7 +141,7 @@
# 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 = 60 * 15
+ CALENDAR_HOME_POLL_INTERVAL = 15 * 60
_STARTUP_PRINCIPAL_PROPFIND = loadRequestBody('sl_startup_principal_propfind')
_STARTUP_PRINCIPALS_REPORT = loadRequestBody('sl_startup_principals_report')
@@ -159,10 +157,10 @@
email = None
- def __init__(self, reactor, host, port, user, auth, calendarHomePollInterval=None):
+ def __init__(self, reactor, root, user, auth, calendarHomePollInterval=None):
self.reactor = reactor
self.agent = AuthHandlerAgent(Agent(self.reactor), auth)
- self.root = 'http://%s:%d/' % (host, port)
+ self.root = root
self.user = user
if calendarHomePollInterval is None:
@@ -268,7 +266,7 @@
d = self._request(
MULTI_STATUS,
'PROPFIND',
- self.root + principalURL[1:],
+ self.root + principalURL[1:].encode('utf-8'),
Headers({
'content-type': ['text/xml'],
'depth': ['0']}),
@@ -350,14 +348,15 @@
response = yield self._eventReport(url, responseHref)
body = yield readBody(response)
res = self._parseMultiStatus(body)[responseHref]
- text = res.getTextProperties()
- etag = text[davxml.getetag]
- try:
- scheduleTag = text[caldavxml.schedule_tag]
- except KeyError:
- scheduleTag = None
- body = text[caldavxml.calendar_data]
- self.eventChanged(responseHref, etag, scheduleTag, body)
+ if res.getStatus() is None or " 404 " not in res.getStatus():
+ text = res.getTextProperties()
+ etag = text[davxml.getetag]
+ try:
+ scheduleTag = text[caldavxml.schedule_tag]
+ except KeyError:
+ scheduleTag = None
+ body = text[caldavxml.calendar_data]
+ self.eventChanged(responseHref, etag, scheduleTag, body)
def eventChanged(self, href, etag, scheduleTag, body):
@@ -392,7 +391,6 @@
def ebCalendars(reason):
reason.trap(IncorrectResponseCode)
d.addErrback(ebCalendars)
- d.addErrback(err, "Unexpected failure during calendar home poll")
return d
@@ -466,8 +464,9 @@
"""
pollCalendarHome = LoopingCall(
self._checkCalendarsForEvents, calendarHome)
- pollCalendarHome.start(self.calendarHomePollInterval, now=False)
+ return pollCalendarHome.start(self.calendarHomePollInterval, now=False)
+
def _newOperation(self, label, deferred):
before = self.reactor.seconds()
msg(type="operation", phase="start", user=self.user, label=label)
@@ -492,11 +491,12 @@
hrefs = principal.getHrefProperties()
calendarHome = hrefs[caldavxml.calendar_home_set].toString()
yield self._checkCalendarsForEvents(calendarHome)
- self._calendarCheckLoop(calendarHome)
- yield self._newOperation("startup", startup())
+ returnValue(calendarHome)
+ calendarHome = yield self._newOperation("startup", startup())
- # XXX Oops, should probably stop sometime.
- yield Deferred()
+ # This completes when the calendar home poll loop completes, which
+ # currently it never will except due to an unexpected error.
+ yield self._calendarCheckLoop(calendarHome)
def _makeSelfAttendee(self):
@@ -559,25 +559,11 @@
d.addCallback(narrowed)
def specific(ignored):
# Now learn about the attendee's availability
- uid = vevent.contents[u'vevent'][0].contents[u'uid'][0].value
- start = vevent.contents[u'vevent'][0].contents[u'dtstart'][0].value
- end = vevent.contents[u'vevent'][0].contents[u'dtend'][0].value
- now = datetime.now()
- d = self._request(
- OK, 'POST', self.root + 'calendars/__uids__/' + self.user + '/outbox/',
- Headers({
- 'content-type': ['text/calendar'],
- 'originator': [self.email],
- 'recipient': ['mailto:' + email]}),
- StringProducer(self._POST_AVAILABILITY % {
- 'attendee': 'mailto:' + email,
- 'organizer': self.email,
- 'vfreebusy-uid': str(uuid4()).upper(),
- 'event-uid': uid.encode('utf-8'),
- 'start': dateTimeToString(start, convertToUTC=True),
- 'end': dateTimeToString(end, convertToUTC=True),
- 'now': dateTimeToString(now, convertToUTC=True)}))
- d.addCallback(readBody)
+ return self.requestAvailability(
+ vevent.contents[u'vevent'][0].contents[u'dtstart'][0].value,
+ vevent.contents[u'vevent'][0].contents[u'dtend'][0].value,
+ [self.email, u'mailto:' + email],
+ [vevent.contents[u'vevent'][0].contents[u'uid'][0].value])
return d
d.addCallback(specific)
def availability(ignored):
@@ -681,7 +667,79 @@
return d
+ def requestAvailability(self, start, end, users, mask=set()):
+ """
+ Issue a VFREEBUSY request for I{roughly} the given date range for the
+ given users. The date range is quantized to one day. Because of this
+ it is an error for the range to span more than 24 hours.
+ @param start: A C{datetime} instance giving the beginning of the
+ desired range.
+
+ @param end: A C{datetime} instance giving the end of the desired range.
+
+ @param users: An iterable of user UUIDs which will be included in the
+ request.
+
+ @param mask: An iterable of event UIDs which are to be ignored for the
+ purposes of this availability lookup.
+
+ @return: A C{Deferred} which fires with a C{dict}. Keys in the dict
+ are user UUIDs (those requested) and values are something else.
+ """
+ outbox = self.root + 'calendars/__uids__/%s/outbox/' % (
+ self.user.encode('utf-8'),)
+
+ if mask:
+ maskStr = u'\r\n'.join(['X-CALENDARSERVER-MASK-UID:' + uid
+ for uid in mask]) + u'\r\n'
+ else:
+ maskStr = u''
+ maskStr = maskStr.encode('utf-8')
+
+ attendeeStr = '\r\n'.join(['ATTENDEE:' + uuid.encode('utf-8')
+ for uuid in users]) + '\r\n'
+
+ # iCal issues 24 hour wide vfreebusy requests, starting and ending at 4am.
+ if start.date() != end.date():
+ msg("Availability request spanning multiple days (%r to %r), "
+ "dropping the end date." % (start, end))
+
+ start = start.replace(hour=0, minute=0, second=0, microsecond=0)
+ end = start + timedelta(hours=24)
+
+ start = dateTimeToString(start, convertToUTC=True)
+ end = dateTimeToString(end, convertToUTC=True)
+ now = dateTimeToString(datetime.now(), convertToUTC=True)
+
+ # XXX Why does it not end up UTC sometimes?
+ if not start.endswith('Z'):
+ start = start + 'Z'
+ if not end.endswith('Z'):
+ end = end + 'Z'
+ if not now.endswith('Z'):
+ now = now + 'Z'
+
+ d = self._request(
+ OK, 'POST', outbox,
+ Headers({
+ 'content-type': ['text/calendar'],
+ 'originator': [self.email],
+ 'recipient': [u', '.join(users).encode('utf-8')]}),
+ StringProducer(self._POST_AVAILABILITY % {
+ 'attendees': attendeeStr,
+ 'summary': (u'Availability for %s' % (', '.join(users),)).encode('utf-8'),
+ 'organizer': self.email.encode('utf-8'),
+ 'vfreebusy-uid': str(uuid4()).upper(),
+ 'event-mask': maskStr,
+ 'start': start,
+ 'end': end,
+ 'now': now}))
+ d.addCallback(readBody)
+ return d
+
+
+
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}"
@@ -689,12 +747,19 @@
def observe(self, event):
if event.get("type") == "response":
- event['url'] = urlunparse(('', '') + urlparse(event['url'])[2:])
+ formatArgs = dict(
+ user=event['user'],
+ method=event['method'],
+ url=urlunparse(('', '') + urlparse(event['url'])[2:]),
+ code=event['code'],
+ duration=event['duration'],
+ )
+
if event['success']:
- event['success'] = self.success
+ formatArgs['success'] = self.success
else:
- event['success'] = self.failure
- print (self.format % event).encode('utf-8')
+ formatArgs['success'] = self.failure
+ print (self.format % formatArgs).encode('utf-8')
def report(self):
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/logger.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/logger.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/logger.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,4 +1,4 @@
-from stats import mean, median, stddev, mad
+from stats import mean, median
class SummarizingMixin(object):
def printHeader(self, fields):
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/population.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/population.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/population.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -20,20 +20,44 @@
certain usage parameters.
"""
+from tempfile import mkdtemp
from itertools import izip
+from twisted.python.filepath import FilePath
from twisted.python.util import FancyEqMixin
from twisted.python.log import msg, err
from stats import mean, median, stddev, mad
+from loadtest.trafficlogger import loggedReactor
from loadtest.logger import SummarizingMixin
from loadtest.ical import SnowLeopard, RequestLogger
from loadtest.profiles import Eventer, Inviter, Accepter
+
+class ProfileType(object, FancyEqMixin):
+ """
+ @ivar profileType: A L{ProfileBase} subclass, or an L{ICalendarUserProfile}
+ implementation.
+
+ @ivar params: A C{dict} which will be passed to C{profileType} as keyword
+ arguments to create a new profile instance.
+ """
+ compareAttributes = ("profileType", "params")
+
+ def __init__(self, profileType, params):
+ self.profileType = profileType
+ self.params = params
+
+
+ def __call__(self, reactor, simulator, client, number):
+ return self.profileType(reactor, simulator, client, number, **self.params)
+
+
+
class ClientType(object, FancyEqMixin):
"""
@ivar clientType: An L{ICalendarClient} implementation
- @ivar profileTypes: A list of L{ICalendarUserProfile} implementations
+ @ivar profileTypes: A list of L{ProfileType} instances
"""
compareAttributes = ("clientType", "profileTypes")
@@ -112,16 +136,19 @@
class CalendarClientSimulator(object):
- def __init__(self, records, populator, parameters, reactor, host, port):
+ def __init__(self, records, populator, parameters, reactor, server):
self._records = records
self.populator = populator
self.reactor = reactor
- self.host = host
- self.port = port
+ self.server = server
self._pop = self.populator.populate(parameters)
self._user = 0
+ def getUserRecord(self, index):
+ return self._records[index]
+
+
def _nextUserNumber(self):
result = self._user
self._user += 1
@@ -135,9 +162,9 @@
auth = HTTPDigestAuthHandler()
auth.add_password(
realm="Test Realm",
- uri="http://%s:%d/" % (self.host, self.port),
- user=user,
- passwd=record.password)
+ uri=self.server,
+ user=user.encode('utf-8'),
+ passwd=record.password.encode('utf-8'))
return user, auth
@@ -147,25 +174,43 @@
user, auth = self._createUser(number)
clientType = self._pop.next()
+ reactor = loggedReactor(self.reactor)
client = clientType.clientType(
- self.reactor, self.host, self.port, user, auth)
+ reactor, self.server, user, auth)
d = client.run()
- d.addCallbacks(self._clientSuccess, self._clientFailure)
+ d.addErrback(self._clientFailure, reactor)
for profileType in clientType.profileTypes:
- profileType(self.reactor, client, number).run()
+ d = profileType(reactor, self, client, number).run()
+ d.addErrback(self._profileFailure, profileType, reactor)
msg(type="status", clientCount=self._user - 1)
- def _clientSuccess(self, result):
- pass
+ def _dumpLogs(self, loggingReactor, reason):
+ path = FilePath(mkdtemp())
+ logstate = loggingReactor.getLogFiles()
+ i = 0
+ for i, log in enumerate(logstate.finished):
+ path.child('%03d.log' % (i,)).setContent(log.getvalue())
+ for i, log in enumerate(logstate.active, i):
+ path.child('%03d.log' % (i,)).setContent(log.getvalue())
+ path.child('reason.log').setContent(reason.getTraceback())
+ return path
- def _clientFailure(self, reason):
- err(reason, "Client stopped with error")
+ def _clientFailure(self, reason, reactor):
+ where = self._dumpLogs(reactor, reason)
+ err(reason, "Client stopped with error; recent traffic in %r" % (
+ where.path,))
+ def _profileFailure(self, reason, profileType, reactor):
+ where = self._dumpLogs(reactor, reason)
+ err(reason, "Profile stopped with error; recent traffic in %r" % (
+ where.path,))
+
+
class SmoothRampUp(object):
def __init__(self, reactor, groups, groupSize, interval):
self.reactor = reactor
@@ -242,7 +287,6 @@
import random
from twisted.internet import reactor
- from twisted.internet.task import LoopingCall
from twisted.python.log import addObserver
from twisted.python.failure import startDebugMode
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/profiles.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/profiles.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/profiles.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -33,11 +33,11 @@
from twisted.python import context
from twisted.python.log import msg
from twisted.python.failure import Failure
-from twisted.internet.defer import succeed, fail
+from twisted.internet.defer import Deferred, succeed, fail
from twisted.internet.task import LoopingCall
from twisted.web.http import PRECONDITION_FAILED
-from stats import mean, median
+from stats import NearFutureDistribution, NormalDistribution, UniformDiscreteDistribution, mean
from loadtest.logger import SummarizingMixin
from loadtest.ical import IncorrectResponseCode
@@ -49,12 +49,18 @@
"""
random = random
- def __init__(self, reactor, client, userNumber):
+ def __init__(self, reactor, simulator, client, userNumber, **params):
self._reactor = reactor
+ self._sim = simulator
self._client = client
self._number = userNumber
+ self.setParameters(**params)
+ def setParameters(self):
+ pass
+
+
def _calendarsOfType(self, calendarType):
return [
cal
@@ -104,16 +110,35 @@
"""
+def loopWithDistribution(reactor, distribution, function):
+ result = Deferred()
+ def repeat(ignored):
+ reactor.callLater(distribution.sample(), iterate)
+
+ def iterate():
+ d = function()
+ d.addCallbacks(repeat, result.errback)
+
+ repeat(None)
+ return result
+
+
+
class Inviter(ProfileBase):
"""
A Calendar user who invites and de-invites other users to events.
"""
+ def setParameters(self,
+ sendInvitationDistribution=NormalDistribution(600, 60),
+ inviteeDistanceDistribution=UniformDiscreteDistribution(range(-10, 11))):
+ self._sendInvitationDistribution = sendInvitationDistribution
+ self._inviteeDistanceDistribution = inviteeDistanceDistribution
+
+
def run(self):
- self._call = LoopingCall(self._invite)
- self._call.clock = self._reactor
- # XXX Base this on something real
- self._call.start(20)
+ return loopWithDistribution(
+ self._reactor, self._sendInvitationDistribution, self._invite)
def _addAttendee(self, event, attendees):
@@ -121,26 +146,29 @@
Create a new attendee to add to the list of attendees for the
given event.
"""
- invitees = set([u'urn:uuid:user%02d' % (self._number,)])
+ selfRecord = self._sim.getUserRecord(self._number)
+ invitees = set([u'urn:uuid:%s' % (selfRecord.uid,)])
for att in attendees:
invitees.add(att.value)
for i in range(10):
- invitee = max(1, int(self.random.gauss(self._number, 3)))
- uuid = u'urn:uuid:user%02d' % (invitee,)
+ invitee = max(
+ 1, self._number + self._inviteeDistanceDistribution.sample())
+ try:
+ record = self._sim.getUserRecord(invitee)
+ except IndexError:
+ continue
+ uuid = u'urn:uuid:%s' % (record.uid,)
if uuid not in invitees:
break
else:
return fail(CannotAddAttendee("Can't find uninvited user to invite."))
- user = u'User %02d' % (invitee,)
- email = u'user%02d at example.com' % (invitee,)
-
attendee = ContentLine(
name=u'ATTENDEE', params=[
- [u'CN', user],
+ [u'CN', record.commonName],
[u'CUTYPE', u'INDIVIDUAL'],
- [u'EMAIL', email],
+ [u'EMAIL', record.email],
[u'PARTSTAT', u'NEEDS-ACTION'],
[u'ROLE', u'REQ-PARTICIPANT'],
[u'RSVP', u'TRUE'],
@@ -201,20 +229,24 @@
lambda reason: reason.trap(CannotAddAttendee))
return self._newOperation("invite", d)
+ # Oops, either no events or no calendars to play with.
+ return succeed(None)
+
class Accepter(ProfileBase):
"""
A Calendar user who accepts invitations to events.
"""
-
- def __init__(self, reactor, client, userNumber):
- ProfileBase.__init__(self, reactor, client, userNumber)
+ def setParameters(self, acceptDelayDistribution=NormalDistribution(1200, 60)):
self._accepting = set()
+ self._acceptDelayDistribution = acceptDelayDistribution
def run(self):
self._subscription = self._client.catalog["eventChanged"].subscribe(self.eventChanged)
+ # TODO: Propagate errors from eventChanged and _acceptInvitation to this Deferred
+ return Deferred()
def eventChanged(self, href):
@@ -236,8 +268,7 @@
for attendee in attendees:
if self._isSelfAttendee(attendee):
if attendee.params[u'PARTSTAT'][0] == 'NEEDS-ACTION':
- # XXX Base this on something real
- delay = self.random.gauss(10, 2)
+ delay = self._acceptDelayDistribution.sample()
self._accepting.add(href)
self._reactor.callLater(
delay, self._acceptInvitation, href, attendee)
@@ -330,12 +361,22 @@
END:VEVENT
END:VCALENDAR
"""))[0]
+ def setParameters(
+ self, interval=25,
+ eventStartDistribution=NearFutureDistribution(),
+ eventDurationDistribution=UniformDiscreteDistribution([
+ 15 * 60, 30 * 60,
+ 45 * 60, 60 * 60,
+ 120 * 60])):
+ self._interval = interval
+ self._eventStartDistribution = eventStartDistribution
+ self._eventDurationDistribution = eventDurationDistribution
+
def run(self):
self._call = LoopingCall(self._addEvent)
self._call.clock = self._reactor
- # XXX Base this on something real
- self._call.start(25)
+ return self._call.start(self._interval)
def _addEvent(self):
@@ -351,10 +392,12 @@
vevent = vcalendar.contents[u'vevent'][0]
tz = vevent.contents[u'created'][0].value.tzinfo
dtstamp = datetime.now(tz)
+ dtstart = datetime.fromtimestamp(self._eventStartDistribution.sample(), tz)
+ dtend = dtstart + timedelta(seconds=self._eventDurationDistribution.sample())
vevent.contents[u'created'][0].value = dtstamp
vevent.contents[u'dtstamp'][0].value = dtstamp
- vevent.contents[u'dtstart'][0].value = dtstamp
- vevent.contents[u'dtend'][0].value = dtstamp + timedelta(hours=1)
+ vevent.contents[u'dtstart'][0].value = dtstart
+ vevent.contents[u'dtend'][0].value = dtend
vevent.contents[u'uid'][0].value = unicode(uuid4())
href = '%s%s.ics' % (
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/request-data/sl_post_availability.request
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/request-data/sl_post_availability.request 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/request-data/sl_post_availability.request 2011-06-30 20:44:13 UTC (rev 7697)
@@ -6,11 +6,9 @@
BEGIN:VFREEBUSY
UID:%(vfreebusy-uid)s
DTEND:%(end)s
-ATTENDEE:%(attendee)s
-DTSTART:%(start)s
-X-CALENDARSERVER-MASK-UID:%(event-uid)s
-DTSTAMP:%(now)s
+%(attendees)sDTSTART:%(start)s
+%(event-mask)sDTSTAMP:%(now)s
ORGANIZER:%(organizer)s
-SUMMARY:Availability for %(attendee)s
+SUMMARY:%(summary)s
END:VFREEBUSY
END:VCALENDAR
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/sim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/sim.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/sim.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -15,33 +15,36 @@
#
##
-from sys import argv
+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
-from twisted.python.log import addObserver, removeObserver
+from twisted.python.log import startLogging, addObserver, removeObserver
from twisted.python.usage import UsageError, Options
from twisted.python.reflect import namedAny
from loadtest.ical import SnowLeopard
from loadtest.profiles import Eventer, Inviter, Accepter
from loadtest.population import (
- Populator, ClientType, PopulationParameters, SmoothRampUp,
+ Populator, ProfileType, ClientType, PopulationParameters, SmoothRampUp,
CalendarClientSimulator)
class _DirectoryRecord(object):
- def __init__(self, uid, password):
+ def __init__(self, uid, password, commonName, email):
self.uid = uid
self.password = password
+ self.commonName = commonName
+ self.email = email
-def recordsFromTextFile(path):
+def recordsFromCSVFile(path):
return [
- _DirectoryRecord(*line.split())
+ _DirectoryRecord(*line.decode('utf-8').split(u','))
for line
in FilePath(path).getContent().splitlines()]
@@ -87,11 +90,21 @@
raise UsageError("--config %s: %s" % (path, e.strerror))
try:
self.config = readPlist(configFile)
- except Exception, e:
- raise UsageError(
- "--config %s: %s" % (path, str(e)))
+ except ExpatError, e:
+ raise UsageError("--config %s: %s" % (path, e))
+ def opt_logfile(self, filename):
+ """
+ Enable normal logging to some file. - for stdout.
+ """
+ if filename == "-":
+ fObj = stdout
+ else:
+ fObj = file(filename, "a")
+ startLogging(fObj, setStdout=False)
+
+
def opt_debug(self):
"""
Enable Deferred and Failure debugging.
@@ -121,7 +134,6 @@
raise UsageError("Specify a configuration file using --config <path>")
-Server = namedtuple('Server', 'host port')
Arrival = namedtuple('Arrival', 'factory parameters')
@@ -130,7 +142,7 @@
A L{LoadSimulator} simulates some configuration of calendar
clients.
- @type server: L{Server}
+ @type server: C{str}
@type arrival: L{Arrival}
@type parameters: L{PopulationParameters}
@@ -160,17 +172,14 @@
except UsageError, e:
raise SystemExit(str(e))
+ server = 'http://127.0.0.1:8008/'
if 'server' in options.config:
- server = Server(
- options.config['server']['host'],
- options.config['server']['port'])
- else:
- server = Server('127.0.0.1', 8008)
+ server = options.config['server']
if 'arrival' in options.config:
- params = options.config['arrival']
- factory = namedAny(params.pop('factory'))
- arrival = Arrival(factory, params)
+ arrival = Arrival(
+ namedAny(options.config['arrival']['factory']),
+ options.config['arrival']['params'])
else:
arrival = Arrival(
SmoothRampUp, dict(groups=10, groupSize=1, interval=3))
@@ -182,7 +191,9 @@
clientConfig["weight"],
ClientType(
namedAny(clientConfig["software"]),
- [namedAny(profile)
+ [ProfileType(
+ namedAny(profile["class"]),
+ cls._convertParams(profile["params"]))
for profile in clientConfig["profiles"]]))
if not parameters.clients:
parameters.addClient(
@@ -202,20 +213,41 @@
return cls(server, arrival, parameters,
observers=observers, records=records)
+ @classmethod
+ def _convertParams(cls, params):
+ """
+ Find parameter values which should be more structured than plistlib is
+ capable of constructing and replace them with the more structured form.
+ Specifically, find keys that end with C{"Distribution"} and convert
+ them into some kind of distribution object using the associated
+ dictionary of keyword arguments.
+ """
+ for k, v in params.iteritems():
+ if k.endswith('Distribution'):
+ params[k] = cls._convertDistribution(v)
+ return params
+
+
@classmethod
+ def _convertDistribution(cls, value):
+ """
+ Construct and return a new distribution object using the type and
+ params specified by C{value}.
+ """
+ return namedAny(value['type'])(**value['params'])
+
+
+ @classmethod
def main(cls, args=None):
simulator = cls.fromCommandLine(args)
raise SystemExit(simulator.run())
def createSimulator(self):
- host = self.server.host
- port = self.server.port
populator = Populator(Random())
return CalendarClientSimulator(
- self.records, populator, self.parameters, self.reactor,
- host, port)
+ self.records, populator, self.parameters, self.reactor, self.server)
def createArrivalPolicy(self):
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_ical.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_ical.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -15,11 +15,19 @@
#
##
+from datetime import datetime
+
from vobject import readComponents
from vobject.base import Component, ContentLine
+from vobject.icalendar import dateTimeToString
+from twisted.python.failure import Failure
from twisted.internet.defer import Deferred
from twisted.trial.unittest import TestCase
+from twisted.web.http import OK, NO_CONTENT, CREATED, MULTI_STATUS
+from twisted.web.http_headers import Headers
+from twisted.web.client import ResponseDone
+from twisted.internet.protocol import ProtocolToConsumerAdapter
from protocol.url import URL
from protocol.webdav.definitions import davxml
@@ -27,7 +35,7 @@
from protocol.caldav.definitions import csxml
from loadtest.ical import Event, Calendar, SnowLeopard
-from httpclient import MemoryConsumer
+from httpclient import MemoryConsumer, StringProducer
EVENT_UID = 'D94F247D-7433-43AF-B84B-ADD684D023B0'
@@ -790,14 +798,48 @@
</multistatus>
"""
-class SnowLeopardTests(TestCase):
+
+class MemoryResponse(object):
+ def __init__(self, version, code, phrase, headers, bodyProducer):
+ self.version = version
+ self.code = code
+ self.phrase = phrase
+ self.headers = headers
+ self.length = bodyProducer.length
+ self._bodyProducer = bodyProducer
+
+
+ def deliverBody(self, protocol):
+ protocol.makeConnection(self._bodyProducer)
+ d = self._bodyProducer.startProducing(ProtocolToConsumerAdapter(protocol))
+ d.addCallback(lambda ignored: protocol.connectionLost(Failure(ResponseDone())))
+
+
+
+class SnowLeopardMixin:
"""
- Tests for L{SnowLeopard}.
+ Mixin for L{TestCase}s for L{SnowLeopard}.
"""
def setUp(self):
- self.client = SnowLeopard(None, "127.0.0.1", 80, None, None)
+ self.user = "user91"
+ self.client = SnowLeopard(None, "http://127.0.0.1/", self.user, None)
+ def interceptRequests(self):
+ requests = []
+ def request(*args):
+ result = Deferred()
+ requests.append((result, args))
+ return result
+ self.client._request = request
+ return requests
+
+
+
+class SnowLeopardTests(SnowLeopardMixin, TestCase):
+ """
+ Tests for L{SnowLeopard}.
+ """
def test_parsePrincipalPROPFINDResponse(self):
"""
L{Principal._parsePROPFINDResponse} accepts an XML document
@@ -871,16 +913,6 @@
self.assertEquals(outbox.ctag, None)
- def interceptRequests(self):
- requests = []
- def request(*args):
- result = Deferred()
- requests.append((result, args))
- return result
- self.client._request = request
- return requests
-
-
def test_changeEventAttendee(self):
"""
SnowLeopard.changeEventAttendee removes one attendee from an
@@ -902,7 +934,7 @@
# iCal PUTs the new VCALENDAR object.
expectedResponseCode, method, url, headers, body = req
self.assertEquals(method, 'PUT')
- self.assertEquals(url, 'http://127.0.0.1:80' + event.url)
+ self.assertEquals(url, 'http://127.0.0.1' + event.url)
self.assertIsInstance(url, str)
self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
@@ -925,6 +957,9 @@
"""
requests = self.interceptRequests()
+ calendar = Calendar(caldavxml.calendar, u'calendar', u'/mumble/', None)
+ self.client._calendars[calendar.url] = calendar
+
vcalendar = list(readComponents(EVENT))[0]
d = self.client.addEvent(u'/mumble/frotz.ics', vcalendar)
@@ -932,10 +967,11 @@
# iCal PUTs the new VCALENDAR object.
expectedResponseCode, method, url, headers, body = req
- self.assertEquals(method, 'PUT')
- self.assertEquals(url, 'http://127.0.0.1:80/mumble/frotz.ics')
+ self.assertEqual(expectedResponseCode, CREATED)
+ self.assertEqual(method, 'PUT')
+ self.assertEqual(url, 'http://127.0.0.1/mumble/frotz.ics')
self.assertIsInstance(url, str)
- self.assertEquals(headers.getRawHeaders('content-type'), ['text/calendar'])
+ self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
consumer = MemoryConsumer()
finished = body.startProducing(consumer)
@@ -944,9 +980,17 @@
list(readComponents(consumer.value()))[0],
vcalendar)
finished.addCallback(cbFinished)
- return finished
+ def requested(ignored):
+ response = MemoryResponse(
+ ('HTTP', '1', '1'), CREATED, "Created", Headers({}),
+ StringProducer(""))
+ result.callback(response)
+ finished.addCallback(requested)
+ return d
+
+
def test_deleteEvent(self):
"""
L{SnowLeopard.deleteEvent} DELETEs the event at the relative
@@ -965,14 +1009,22 @@
result, req = requests.pop()
expectedResponseCode, method, url = req
- self.assertEquals(method, 'DELETE')
- self.assertEquals(url, 'http://127.0.0.1:80' + event.url)
+
+ self.assertEqual(expectedResponseCode, NO_CONTENT)
+ self.assertEqual(method, 'DELETE')
+ self.assertEqual(url, 'http://127.0.0.1' + event.url)
self.assertIsInstance(url, str)
self.assertNotIn(event.url, self.client._events)
self.assertNotIn(u'bar.ics', calendar.events)
+ response = MemoryResponse(
+ ('HTTP', '1', '1'), NO_CONTENT, "No Content", None,
+ StringProducer(""))
+ result.callback(response)
+ return d
+
def assertComponentsEqual(self, first, second):
self.assertEquals(first.name, second.name, "Component names not equal")
self.assertEquals(first.behavior, second.behavior, "Component behaviors not equal")
@@ -1003,3 +1055,167 @@
self.assertEquals(
first.singletonparams, second.singletonparams,
"ContentLine singletonparams not equal")
+
+
+
+class UpdateCalendarTests(SnowLeopardMixin, TestCase):
+ """
+ Tests for L{SnowLeopard._updateCalendar}.
+ """
+
+ _CALENDAR_PROPFIND_RESPONSE_BODY = """\
+<?xml version='1.0' encoding='UTF-8'?>
+<multistatus xmlns='DAV:'>
+ <response>
+ <href>/something/anotherthing.ics</href>
+ <propstat>
+ <prop>
+ <resourcetype>
+ <collection/>
+ </resourcetype>
+ <getetag>"None"</getetag>
+ </prop>
+ <status>HTTP/1.1 200 OK</status>
+ </propstat>
+ <propstat>
+ <prop>
+ </prop>
+ <status>HTTP/1.1 404 Not Found</status>
+ </propstat>
+ </response>
+ <response>
+ <href>/something/else.ics</href>
+ <propstat>
+ <prop>
+ <resourcetype>
+ <collection/>
+ </resourcetype>
+ <getetag>"None"</getetag>
+ </prop>
+ <status>HTTP/1.1 200 OK</status>
+ </propstat>
+ </response>
+</multistatus>
+"""
+ _CALENDAR_REPORT_RESPONSE_BODY = """\
+<?xml version='1.0' encoding='UTF-8'?>
+<multistatus xmlns='DAV:'>
+ <response>
+ <href>/something/anotherthing.ics</href>
+ <status>HTTP/1.1 404 Not Found</status>
+ </response>
+</multistatus>
+"""
+ def test_eventMissing(self):
+ """
+ If an event included in the calendar PROPFIND response no longer exists
+ by the time a REPORT is issued for that event, the 404 is handled and
+ the rest of the normal update logic for that event is skipped.
+ """
+ requests = self.interceptRequests()
+
+ calendar = Calendar(None, 'calendar', '/something/', None)
+ self.client._calendars[calendar.url] = calendar
+ self.client._updateCalendar(calendar)
+ result, req = requests.pop(0)
+ expectedResponseCode, method, url, headers, body = req
+ self.assertEqual('PROPFIND', method)
+ self.assertEqual('http://127.0.0.1/something/', url)
+ self.assertEqual(MULTI_STATUS, expectedResponseCode)
+
+ result.callback(
+ MemoryResponse(
+ ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+ StringProducer(self._CALENDAR_PROPFIND_RESPONSE_BODY)))
+
+ result, req = requests.pop(0)
+ expectedResponseCode, method, url, headers, body = req
+ self.assertEqual('REPORT', method)
+ self.assertEqual('http://127.0.0.1/something/', url)
+ self.assertEqual(MULTI_STATUS, expectedResponseCode)
+
+ # Someone else comes along and gets rid of the event
+ del self.client._events["/something/anotherthing.ics"]
+
+ result.callback(
+ MemoryResponse(
+ ('HTTP', '1', '1'), MULTI_STATUS, "Multi-status", None,
+ StringProducer(self._CALENDAR_REPORT_RESPONSE_BODY)))
+
+ # Verify that processing proceeded to the response after the one with a
+ # 404 status.
+ self.assertIn('/something/else.ics', self.client._events)
+
+
+
+class VFreeBusyTests(SnowLeopardMixin, TestCase):
+ """
+ Tests for L{SnowLeopard.requestAvailability}.
+ """
+ def test_requestAvailability(self):
+ """
+ L{SnowLeopard.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.
+ """
+ self.client.uuid = u'urn:uuid:user01'
+ self.client.email = u'mailto:user01 at example.com'
+ requests = self.interceptRequests()
+
+ start = datetime(2011, 6, 10, 10, 45, 0)
+ end = datetime(2011, 6, 10, 11, 15, 0)
+ d = self.client.requestAvailability(
+ start, end, [u"urn:uuid:user05", u"urn:uuid:user10"])
+
+ result, req = requests.pop(0)
+ expectedResponseCode, method, url, headers, body = req
+
+ self.assertEqual(OK, expectedResponseCode)
+ self.assertEqual('POST', method)
+ self.assertEqual(
+ 'http://127.0.0.1/calendars/__uids__/%s/outbox/' % (self.user,),
+ url)
+
+ self.assertEqual(headers.getRawHeaders('originator'), ['mailto:user01 at example.com'])
+ self.assertEqual(headers.getRawHeaders('recipient'), ['urn:uuid:user05, urn:uuid:user10'])
+ self.assertEqual(headers.getRawHeaders('content-type'), ['text/calendar'])
+
+ consumer = MemoryConsumer()
+ finished = body.startProducing(consumer)
+ def cbFinished(ignored):
+ vevent = list(readComponents(consumer.value()))[0]
+ uid = vevent.contents[u'vfreebusy'][0].contents[u'uid'][0].value.encode('utf-8')
+ dtstamp = vevent.contents[u'vfreebusy'][0].contents[u'dtstamp'][0].value
+ dtstamp = dateTimeToString(dtstamp, False)
+ self.assertEqual(
+"""\
+BEGIN:VCALENDAR
+CALSCALE:GREGORIAN
+VERSION:2.0
+METHOD:REQUEST
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VFREEBUSY
+UID:%(uid)s
+DTEND:20110611T000000Z
+ATTENDEE:urn:uuid:user05
+ATTENDEE:urn:uuid:user10
+DTSTART:20110610T000000Z
+DTSTAMP:%(dtstamp)s
+ORGANIZER:mailto:user01 at example.com
+SUMMARY:Availability for urn:uuid:user05, urn:uuid:user10
+END:VFREEBUSY
+END:VCALENDAR
+""".replace('\n', '\r\n') % {'uid': uid, 'dtstamp': dtstamp},consumer.value())
+
+ finished.addCallback(cbFinished)
+
+ def requested(ignored):
+ response = MemoryResponse(
+ ('HTTP', '1', '1'), OK, "Ok", Headers({}),
+ StringProducer(""))
+ result.callback(response)
+ finished.addCallback(requested)
+
+ return d
+
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_profiles.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_profiles.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_profiles.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -30,6 +30,7 @@
from twisted.web.client import Response
from loadtest.profiles import Eventer, Inviter, Accepter
+from loadtest.population import Populator, CalendarClientSimulator
from loadtest.ical import IncorrectResponseCode, Calendar, Event, BaseClient
SIMPLE_EVENT = """\
@@ -150,8 +151,24 @@
"""
+class AnyUser(object):
+ def __getitem__(self, index):
+ return _AnyRecord(index)
+
+class _AnyRecord(object):
+ def __init__(self, index):
+ self.uid = u"user%02d" % (index,)
+ self.password = u"user%02d" % (index,)
+ self.commonName = u"User %02d" % (index,)
+ self.email = u"user%02d at example.com" % (index,)
+
+
class Deterministic(object):
+ def __init__(self, value=None):
+ self.value = value
+
+
def gauss(self, mean, stddev):
"""
Pretend to return a value from a gaussian distribution with mu
@@ -165,7 +182,11 @@
return sequence[0]
+ def sample(self):
+ return self.value
+
+
class StubClient(BaseClient):
"""
Stand in for an iCalendar client.
@@ -187,7 +208,7 @@
def deleteEvent(self, href):
- event = self._events.pop(href)
+ del self._events[href]
calendar, uid = href.rsplit('/', 1)
del self._calendars[calendar + '/'].events[uid]
@@ -219,10 +240,25 @@
+class SequentialDistribution(object):
+ def __init__(self, values):
+ self.values = values
+
+
+ def sample(self):
+ return self.values.pop(0)
+
+
+
class InviterTests(TestCase):
"""
Tests for loadtest.profiles.Inviter.
"""
+ def setUp(self):
+ self.sim = CalendarClientSimulator(
+ AnyUser(), Populator(None), None, None, None)
+
+
def _simpleAccount(self, userNumber, eventText):
vevent = list(readComponents(eventText))[0]
calendar = Calendar(
@@ -245,7 +281,7 @@
vevent, event, calendar, client = self._simpleAccount(
userNumber, SIMPLE_EVENT)
calendar.resourceType = caldavxml.schedule_inbox
- inviter = Inviter(None, client, userNumber)
+ inviter = Inviter(None, self.sim, client, userNumber)
inviter._invite()
self.assertNotIn(u'attendee', vevent.contents[u'vevent'][0].contents)
@@ -257,7 +293,7 @@
"""
userNumber = 13
client = StubClient(userNumber)
- inviter = Inviter(None, client, userNumber)
+ inviter = Inviter(None, self.sim, client, userNumber)
inviter._invite()
self.assertEquals(client._events, {})
self.assertEquals(client._calendars, {})
@@ -273,7 +309,7 @@
vevent, event, calendar, client = self._simpleAccount(
userNumber, SIMPLE_EVENT)
event.vevent = event.etag = event.scheduleTag = None
- inviter = Inviter(None, client, userNumber)
+ inviter = Inviter(None, self.sim, client, userNumber)
inviter._invite()
self.assertEquals(client._events, {event.url: event})
self.assertEquals(client._calendars, {calendar.url: calendar})
@@ -287,8 +323,8 @@
userNumber = 16
vevent, event, calendar, client = self._simpleAccount(
userNumber, SIMPLE_EVENT)
- inviter = Inviter(Clock(), client, userNumber)
- inviter.random = Deterministic()
+ inviter = Inviter(Clock(), self.sim, client, userNumber)
+ inviter.setParameters(inviteeDistanceDistribution=Deterministic(1))
inviter._invite()
attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
self.assertEquals(len(attendees), 1)
@@ -312,11 +348,10 @@
selfNumber, SIMPLE_EVENT)
otherNumber = 20
- values = [selfNumber, otherNumber]
+ values = [selfNumber - selfNumber, otherNumber - selfNumber]
- inviter = Inviter(Clock(), client, selfNumber)
- inviter.random = Deterministic()
- inviter.random.gauss = lambda mu, sigma: values.pop(0)
+ inviter = Inviter(Clock(), self.sim, client, selfNumber)
+ inviter.setParameters(inviteeDistanceDistribution=SequentialDistribution(values))
inviter._invite()
attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
self.assertEquals(len(attendees), 1)
@@ -342,11 +377,10 @@
invitee = vevent.contents[u'vevent'][0].contents[u'attendee'][0]
inviteeNumber = int(invitee.params[u'CN'][0].split()[1])
anotherNumber = inviteeNumber + 5
- values = [inviteeNumber, anotherNumber]
+ values = [inviteeNumber - selfNumber, anotherNumber - selfNumber]
- inviter = Inviter(Clock(), client, selfNumber)
- inviter.random = Deterministic()
- inviter.random.gauss = lambda mu, sigma: values.pop(0)
+ inviter = Inviter(Clock(), self.sim, client, selfNumber)
+ inviter.setParameters(inviteeDistanceDistribution=SequentialDistribution(values))
inviter._invite()
attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
self.assertEquals(len(attendees), 3)
@@ -368,10 +402,9 @@
selfNumber = 1
vevent, event, calendar, client = self._simpleAccount(
selfNumber, INVITED_EVENT)
- inviter = Inviter(Clock(), client, selfNumber)
- inviter.random = Deterministic()
+ inviter = Inviter(Clock(), self.sim, client, selfNumber)
# Always return a user number which has already been invited.
- inviter.random.gauss = lambda mu, sigma: 2
+ inviter.setParameters(inviteeDistanceDistribution=Deterministic(2 - selfNumber))
inviter._invite()
attendees = vevent.contents[u'vevent'][0].contents[u'attendee']
self.assertEquals(len(attendees), 2)
@@ -386,7 +419,7 @@
selfNumber = 2
vevent, event, calendar, client = self._simpleAccount(
selfNumber, INVITED_EVENT)
- inviter = Inviter(None, client, selfNumber)
+ inviter = Inviter(None, self.sim, client, selfNumber)
# Try to send an invitation, but with only one event on the
# calendar, of which we are not the organizer. It should be
# unchanged afterwards.
@@ -402,13 +435,18 @@
"""
Tests for loadtest.profiles.Accepter.
"""
+ def setUp(self):
+ self.sim = CalendarClientSimulator(
+ AnyUser(), Populator(None), None, None, None)
+
+
def test_ignoreEventOnUnknownCalendar(self):
"""
If an event on an unknown calendar changes, it is ignored.
"""
userNumber = 13
client = StubClient(userNumber)
- accepter = Accepter(None, client, userNumber)
+ accepter = Accepter(None, self.sim, client, userNumber)
accepter.eventChanged('/some/calendar/1234.ics')
@@ -423,7 +461,7 @@
caldavxml.schedule_inbox, u'inbox', calendarURL, None)
client = StubClient(userNumber)
client._calendars[calendarURL] = calendar
- accepter = Accepter(None, client, userNumber)
+ accepter = Accepter(None, self.sim, client, userNumber)
accepter.eventChanged(calendarURL + '1234.ics')
@@ -442,7 +480,7 @@
client._calendars[calendarURL] = calendar
event = Event(calendarURL + u'1234.ics', None, vevent)
client._events[event.url] = event
- accepter = Accepter(None, client, userNumber)
+ accepter = Accepter(None, self.sim, client, userNumber)
accepter.eventChanged(event.url)
@@ -465,7 +503,7 @@
client._calendars[calendarURL] = calendar
event = Event(calendarURL + u'1234.ics', None, vevent)
client._events[event.url] = event
- accepter = Accepter(clock, client, userNumber)
+ accepter = Accepter(clock, self.sim, client, userNumber)
accepter.random = Deterministic()
accepter.random.gauss = lambda mu, sigma: randomDelay
accepter.eventChanged(event.url)
@@ -503,9 +541,8 @@
inboxEvent = Event(inboxURL + u'4321.ics', None, vevent)
client._setEvent(inboxEvent.url, inboxEvent)
- accepter = Accepter(clock, client, userNumber)
- accepter.random = Deterministic()
- accepter.random.gauss = lambda mu, sigma: randomDelay
+ accepter = Accepter(clock, self.sim, client, userNumber)
+ accepter.setParameters(Deterministic(randomDelay))
accepter.eventChanged(event.url)
clock.advance(randomDelay)
@@ -540,9 +577,8 @@
client._calendars[calendarURL] = calendar
event = Event(calendarURL + u'1234.ics', None, vevent)
client._events[event.url] = event
- accepter = Accepter(clock, client, userNumber)
- accepter.random = Deterministic()
- accepter.random.gauss = lambda mu, sigma: randomDelay
+ accepter = Accepter(clock, self.sim, client, userNumber)
+ accepter.setParameters(Deterministic(randomDelay))
accepter.eventChanged(event.url)
clock.advance(randomDelay)
@@ -584,9 +620,8 @@
event = Event(calendarURL + u'1234.ics', None, vevent)
client._setEvent(event.url, event)
- accepter = Accepter(clock, client, userNumber)
- accepter.random = Deterministic()
- accepter.random.gauss = lambda mu, sigma: randomDelay
+ accepter = Accepter(clock, self.sim, client, userNumber)
+ accepter.setParameters(Deterministic(randomDelay))
client.rescheduled.add(event.url)
@@ -602,6 +637,11 @@
Tests for loadtest.profiles.Eventer, a profile which adds new
events on calendars.
"""
+ def setUp(self):
+ self.sim = CalendarClientSimulator(
+ AnyUser(), Populator(None), None, None, None)
+
+
def test_doNotAddEventOnInbox(self):
"""
When the only calendar is a schedule inbox, no attempt is made
@@ -612,7 +652,7 @@
client = StubClient(21)
client._calendars.update({calendar.url: calendar})
- eventer = Eventer(None, client, None)
+ eventer = Eventer(None, self.sim, client, None)
eventer._addEvent()
self.assertEquals(client._events, {})
@@ -628,7 +668,7 @@
client = StubClient(31)
client._calendars.update({calendar.url: calendar})
- eventer = Eventer(Clock(), client, None)
+ eventer = Eventer(Clock(), self.sim, client, None)
eventer._addEvent()
self.assertEquals(len(client._events), 1)
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_sim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_sim.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_sim.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -15,39 +15,33 @@
#
##
-from operator import setitem
from plistlib import writePlistToString
-from zope.interface.verify import verifyClass
-
-from twisted.python.log import LogPublisher, theLogPublisher, msg
+from twisted.python.log import msg
from twisted.python.usage import UsageError
from twisted.python.filepath import FilePath
from twisted.trial.unittest import TestCase
-from twisted.internet.defer import succeed
-from twisted.internet.task import Clock
-from twistedcaldav.directory.idirectory import IDirectoryService
-from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
+from twistedcaldav.directory.directory import DirectoryRecord
+from stats import NormalDistribution
from loadtest.ical import SnowLeopard
from loadtest.profiles import Eventer, Inviter, Accepter
from loadtest.population import (
SmoothRampUp, ClientType, PopulationParameters, Populator, CalendarClientSimulator,
- SimpleStatistics)
+ ProfileType, SimpleStatistics)
from loadtest.sim import (
- Server, Arrival, SimOptions, LoadSimulator, LagTrackingReactor, main)
+ Arrival, SimOptions, LoadSimulator, LagTrackingReactor)
VALID_CONFIG = {
- 'server': {
- 'host': '127.0.0.1',
- 'port': 8008,
- },
+ 'server': 'tcp:127.0.0.1:8008',
'arrival': {
'factory': 'loadtest.population.SmoothRampUp',
- 'groups': 10,
- 'groupSize': 1,
- 'interval': 3,
+ 'params': {
+ 'groups': 10,
+ 'groupSize': 1,
+ 'interval': 3,
+ },
},
}
@@ -120,7 +114,7 @@
"""
calsim = CalendarClientSimulator(
[self._user('alice'), self._user('bob'), self._user('carol')],
- Populator(None), None, None, 'example.org', 1234)
+ Populator(None), None, None, 'http://example.org:1234/')
users = sorted([
calsim._createUser(0)[0],
calsim._createUser(1)[0],
@@ -136,7 +130,7 @@
"""
calsim = CalendarClientSimulator(
[self._user('alice')],
- Populator(None), None, None, 'example.org', 1234)
+ Populator(None), None, None, 'http://example.org:1234/')
user, auth = calsim._createUser(0)
self.assertEqual(
auth.passwd.find_user_password('Test Realm', 'http://example.org:1234/')[1],
@@ -202,16 +196,14 @@
with its own reactor and host and port information from the
configuration file.
"""
- host = '127.0.0.7'
- port = 1243
+ server = 'http://127.0.0.7:1243/'
reactor = object()
- sim = LoadSimulator(Server(host, port), None, None, reactor=reactor)
+ sim = LoadSimulator(server, None, None, reactor=reactor)
calsim = sim.createSimulator()
self.assertIsInstance(calsim, CalendarClientSimulator)
self.assertIsInstance(calsim.reactor, LagTrackingReactor)
self.assertIdentical(calsim.reactor._reactor, reactor)
- self.assertEquals(calsim.host, host)
- self.assertEquals(calsim.port, port)
+ self.assertEquals(calsim.server, server)
def test_loadAccountsFromFile(self):
@@ -219,10 +211,10 @@
L{LoadSimulator.
"""
accounts = FilePath(self.mktemp())
- accounts.setContent("foo bar\nbaz quux\n")
+ accounts.setContent("foo,bar,baz,quux\nfoo2,bar2,baz2,quux2\n")
config = VALID_CONFIG.copy()
config["accounts"] = {
- "loader": "loadtest.sim.recordsFromTextFile",
+ "loader": "loadtest.sim.recordsFromCSVFile",
"params": {
"path": accounts.path},
}
@@ -232,8 +224,12 @@
self.assertEqual(2, len(sim.records))
self.assertEqual(sim.records[0].uid, 'foo')
self.assertEqual(sim.records[0].password, 'bar')
- self.assertEqual(sim.records[1].uid, 'baz')
- self.assertEqual(sim.records[1].password, 'quux')
+ self.assertEqual(sim.records[0].commonName, 'baz')
+ self.assertEqual(sim.records[0].email, 'quux')
+ self.assertEqual(sim.records[1].uid, 'foo2')
+ self.assertEqual(sim.records[1].password, 'bar2')
+ self.assertEqual(sim.records[1].commonName, 'baz2')
+ self.assertEqual(sim.records[1].email, 'quux2')
def test_loadServerConfig(self):
@@ -243,13 +239,9 @@
"""
config = FilePath(self.mktemp())
config.setContent(writePlistToString({
- "server": {
- "host": "127.0.0.1",
- "port": 1234,
- },
- }))
+ "server": "https://127.0.0.3:8432/"}))
sim = LoadSimulator.fromCommandLine(['--config', config.path])
- self.assertEquals(sim.server, Server("127.0.0.1", 1234))
+ self.assertEquals(sim.server, "https://127.0.0.3:8432/")
def test_loadArrivalConfig(self):
@@ -261,9 +253,11 @@
config.setContent(writePlistToString({
"arrival": {
"factory": "loadtest.population.SmoothRampUp",
- "groups": 10,
- "groupSize": 1,
- "interval": 3,
+ "params": {
+ "groups": 10,
+ "groupSize": 1,
+ "interval": 3,
+ },
},
}))
sim = LoadSimulator.fromCommandLine(['--config', config.path])
@@ -302,12 +296,25 @@
config.setContent(writePlistToString({
"clients": [{
"software": "loadtest.ical.SnowLeopard",
- "profiles": ["loadtest.profiles.Eventer"],
+ "profiles": [{
+ "params": {
+ "interval": 25,
+ "eventStartDistribution": {
+ "type": "stats.NormalDistribution",
+ "params": {
+ "mu": 123,
+ "sigma": 456,
+ }}},
+ "class": "loadtest.profiles.Eventer"}],
"weight": 3,
}]}))
+
sim = LoadSimulator.fromCommandLine(['--config', config.path])
expectedParameters = PopulationParameters()
- expectedParameters.addClient(3, ClientType(SnowLeopard, [Eventer]))
+ expectedParameters.addClient(
+ 3, ClientType(SnowLeopard, [ProfileType(Eventer, {
+ "interval": 25,
+ "eventStartDistribution": NormalDistribution(123, 456)})]))
self.assertEquals(sim.parameters, expectedParameters)
@@ -338,6 +345,7 @@
self.assertEquals(len(sim.observers), 1)
self.assertIsInstance(sim.observers[0], SimpleStatistics)
+
def test_observeRunReport(self):
"""
Each log observer is added to the log publisher before the
@@ -346,7 +354,7 @@
"""
observers = [Observer()]
sim = LoadSimulator(
- Server('example.com', 123),
+ "http://example.com:123/",
Arrival(lambda reactor: NullArrival(), {}),
None, observers, reactor=Reactor())
sim.run()
Copied: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_trafficlogger.py (from rev 7695, CalendarServer/trunk/contrib/performance/loadtest/test_trafficlogger.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_trafficlogger.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/test_trafficlogger.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,207 @@
+##
+# Copyright (c) 2011 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+##
+
+from zope.interface import Interface, implements
+
+from twisted.internet.protocol import ClientFactory, Protocol
+from twisted.trial.unittest import TestCase
+from twisted.test.proto_helpers import StringTransport, MemoryReactor
+from twisted.protocols.wire import Discard
+
+from loadtest.trafficlogger import _TrafficLoggingFactory, loggedReactor
+
+
+class IProbe(Interface):
+ """
+ An interface which can be used to verify some interface-related behavior of
+ L{loggedReactor}.
+ """
+ def probe():
+ pass
+
+
+class Probe(object):
+ implements(IProbe)
+
+ _probed = False
+
+ def __init__(self, result=None):
+ self._result = result
+
+ def probe(self):
+ self._probed = True
+ return self._result
+
+
+class TrafficLoggingReactorTests(TestCase):
+ """
+ Tests for L{loggedReactor}.
+ """
+ def test_nothing(self):
+ """
+ L{loggedReactor} returns the object passed to it, if the object passed
+ to it doesn't provide any interfaces. This is mostly for testing
+ convenience rather than a particularly useful feature.
+ """
+ probe = object()
+ self.assertIdentical(probe, loggedReactor(probe))
+
+
+ def test_interfaces(self):
+ """
+ The object returned by L{loggedReactor} provides all of the interfaces
+ provided by the object passed to it.
+ """
+ probe = Probe()
+ reactor = loggedReactor(probe)
+ self.assertTrue(IProbe.providedBy(reactor))
+
+
+ def test_passthrough(self):
+ """
+ Methods on interfaces on the object passed to L{loggedReactor} can be
+ called by calling them on the object returned by L{loggedReactor}.
+ """
+ expected = object()
+ probe = Probe(expected)
+ reactor = loggedReactor(probe)
+ result = reactor.probe()
+ self.assertTrue(probe._probed)
+ self.assertIdentical(expected, result)
+
+
+ def test_connectTCP(self):
+ """
+ Called on the object returned by L{loggedReactor}, C{connectTCP} calls
+ the wrapped reactor's C{connectTCP} method with the original factory
+ wrapped in a L{_TrafficLoggingFactory}.
+ """
+ class RecordDataProtocol(Protocol):
+ def dataReceived(self, data):
+ self.data = data
+ proto = RecordDataProtocol()
+ factory = ClientFactory()
+ factory.protocol = lambda: proto
+ reactor = MemoryReactor()
+ logged = loggedReactor(reactor)
+ logged.connectTCP('192.168.1.2', 1234, factory, 21, '127.0.0.2')
+ [(host, port, factory, timeout, bindAddress)] = reactor.tcpClients
+ self.assertEqual('192.168.1.2', host)
+ self.assertEqual(1234, port)
+ self.assertIsInstance(factory, _TrafficLoggingFactory)
+ self.assertEqual(21, timeout)
+ self.assertEqual('127.0.0.2', bindAddress)
+
+ # Verify that the factory and protocol specified are really being used
+ protocol = factory.buildProtocol(None)
+ protocol.makeConnection(None)
+ protocol.dataReceived("foo")
+ self.assertEqual(proto.data, "foo")
+
+
+ def test_getLogFiles(self):
+ """
+ The reactor returned by L{loggedReactor} has a C{getLogFiles} method
+ which returns a L{logstate} instance containing the active and
+ completed log files tracked by the logging wrapper.
+ """
+ wrapped = ClientFactory()
+ wrapped.protocol = Discard
+ reactor = MemoryReactor()
+ logged = loggedReactor(reactor)
+ logged.connectTCP('127.0.0.1', 1234, wrapped)
+ factory = reactor.tcpClients[0][2]
+
+ finished = factory.buildProtocol(None)
+ finished.makeConnection(StringTransport())
+ finished.dataReceived('finished')
+ finished.connectionLost(None)
+
+ active = factory.buildProtocol(None)
+ active.makeConnection(StringTransport())
+ active.dataReceived('active')
+
+ logs = logged.getLogFiles()
+ self.assertEqual(1, len(logs.finished))
+ self.assertIn('finished', logs.finished[0].getvalue())
+ self.assertEqual(1, len(logs.active))
+ self.assertIn('active', logs.active[0].getvalue())
+
+
+
+class TrafficLoggingFactoryTests(TestCase):
+ """
+ Tests for L{_TrafficLoggingFactory}.
+ """
+ def setUp(self):
+ self.wrapped = ClientFactory()
+ self.wrapped.protocol = Discard
+ self.factory = _TrafficLoggingFactory(self.wrapped)
+
+
+ def test_receivedBytesLogged(self):
+ """
+ When bytes are delivered through a protocol created by
+ L{_TrafficLoggingFactory}, they are added to a log kept on that
+ factory.
+ """
+ protocol = self.factory.buildProtocol(None)
+
+ # The factory should now have a new StringIO log file
+ self.assertEqual(1, len(self.factory.logs))
+
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+
+ protocol.dataReceived("hello, world")
+ self.assertEqual(
+ "*\nC 0: 'hello, world'\n", self.factory.logs[0].getvalue())
+
+
+ def test_finishedLogs(self):
+ """
+ When connections are lost, the corresponding log files are moved into
+ C{_TrafficLoggingFactory.finishedLogs}.
+ """
+ protocol = self.factory.buildProtocol(None)
+ transport = StringTransport()
+ protocol.makeConnection(transport)
+ logfile = self.factory.logs[0]
+ protocol.connectionLost(None)
+ self.assertEqual(0, len(self.factory.logs))
+ self.assertEqual([logfile], self.factory.finishedLogs)
+
+
+ def test_finishedLogsLimit(self):
+ """
+ Only the most recent C{_TrafficLoggingFactory.LOGFILE_LIMIT} logfiles
+ are kept in C{_TrafficLoggingFactory.finishedLogs}.
+ """
+ self.factory.LOGFILE_LIMIT = 2
+ first = self.factory.buildProtocol(None)
+ first.makeConnection(StringTransport())
+ second = self.factory.buildProtocol(None)
+ second.makeConnection(StringTransport())
+ third = self.factory.buildProtocol(None)
+ third.makeConnection(StringTransport())
+
+ second.connectionLost(None)
+ first.connectionLost(None)
+ third.connectionLost(None)
+
+ self.assertEqual(
+ [first.logfile, third.logfile], self.factory.finishedLogs)
Copied: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/trafficlogger.py (from rev 7695, CalendarServer/trunk/contrib/performance/loadtest/trafficlogger.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/trafficlogger.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/loadtest/trafficlogger.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,114 @@
+##
+# Copyright (c) 2011 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.
+#
+##
+
+"""
+This module implements a reactor wrapper which will cause all traffic on
+connections set up using that reactor to be logged.
+"""
+
+__all__ = ['loggedReactor']
+
+from weakref import ref
+from StringIO import StringIO
+from collections import namedtuple
+
+from zope.interface import providedBy
+
+from twisted.python.components import proxyForInterface
+from twisted.internet.interfaces import IReactorTCP
+from twisted.protocols.policies import WrappingFactory, TrafficLoggingProtocol
+
+
+logstate = namedtuple('logstate', 'active finished')
+
+
+def loggedReactor(reactor):
+ """
+ Construct and return a wrapper around the given C{reactor} which provides
+ all of the same interfaces, but which will log all traffic over outgoing
+ TCP connections it establishes.
+ """
+ bases = []
+ for iface in providedBy(reactor):
+ if iface is IReactorTCP:
+ bases.append(_TCPTrafficLoggingReactor)
+ else:
+ bases.append(proxyForInterface(iface, '_reactor'))
+ if bases:
+ return type('(Logged Reactor)', tuple(bases), {})(reactor)
+ return reactor
+
+
+class _TCPTrafficLoggingReactor(proxyForInterface(IReactorTCP, '_reactor')):
+ """
+ A mixin for a reactor wrapper which defines C{connectTCP} so as to cause
+ traffic to be logged.
+ """
+ _factories = None
+
+ @property
+ def factories(self):
+ if self._factories is None:
+ self._factories = []
+ return self._factories
+
+
+ def getLogFiles(self):
+ active = []
+ finished = []
+ for factoryref in self.factories:
+ factory = factoryref()
+ active.extend(factory.logs)
+ finished.extend(factory.finishedLogs)
+ return logstate(active, finished)
+
+
+ def connectTCP(self, host, port, factory, *args, **kwargs):
+ wrapper = _TrafficLoggingFactory(factory)
+ self.factories.append(ref(wrapper, self.factories.remove))
+ return self._reactor.connectTCP(
+ host, port, wrapper, *args, **kwargs)
+
+
+class _TrafficLoggingFactory(WrappingFactory):
+ """
+ A wrapping factory which applies L{TrafficLoggingProtocolWrapper}.
+ """
+ LOGFILE_LIMIT = 20
+
+ protocol = TrafficLoggingProtocol
+
+ noisy = False
+
+ def __init__(self, wrappedFactory):
+ WrappingFactory.__init__(self, wrappedFactory)
+ self.logs = []
+ self.finishedLogs = []
+
+
+ def unregisterProtocol(self, protocol):
+ WrappingFactory.unregisterProtocol(self, protocol)
+ self.logs.remove(protocol.logfile)
+ self.finishedLogs.append(protocol.logfile)
+ del self.finishedLogs[:-self.LOGFILE_LIMIT]
+
+
+ def buildProtocol(self, addr):
+ logfile = StringIO()
+ self.logs.append(logfile)
+ return self.protocol(
+ self, self.wrappedFactory.buildProtocol(addr), logfile, None, 0)
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/run.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/run.sh 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/run.sh 2011-06-30 20:44:13 UTC (rev 7697)
@@ -20,6 +20,6 @@
source support/shell.sh
popd
-export PYTHONPATH=$PYTHONPATH:~/Projects/CalDAVClientLibrary/trunk/src
+export PYTHONPATH=$PYTHONPATH:~/Projects/CalendarServer/CalDAVClientLibrary/src
exec "$@"
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/setbackend.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/setbackend.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/setbackend.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -22,7 +22,6 @@
import sys
from xml.etree import ElementTree
-from xml.etree import ElementPath
def main():
conf = ElementTree.parse(file(sys.argv[1]))
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/stats.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/stats.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/stats.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -14,6 +14,14 @@
# limitations under the License.
##
+import random, time, datetime
+
+import pytz
+
+from zope.interface import Interface, implements
+
+from twisted.python.util import FancyEqMixin
+
import sqlparse
NANO = 1000000000.0
@@ -200,3 +208,157 @@
def summarize(self, samples):
return _Statistic.summarize(self, self.squash(samples))
+
+
+def quantize(data):
+ """
+ Given some continuous data, quantize it into appropriately sized
+ discrete buckets (eg, as would be suitable for constructing a
+ histogram of the values).
+ """
+ #buckets = {}
+ return []
+
+
+class IPopulation(Interface):
+ def sample():
+ pass
+
+
+class UniformDiscreteDistribution(object, FancyEqMixin):
+ """
+
+ """
+ implements(IPopulation)
+
+ compareAttributes = ['_values']
+
+ def __init__(self, values):
+ self._values = values
+ self._refill()
+
+
+ def _refill(self):
+ self._remaining = self._values[:]
+ random.shuffle(self._remaining)
+
+
+ def sample(self):
+ if not self._remaining:
+ self._refill()
+ return self._remaining.pop()
+
+
+
+class LogNormalDistribution(object, FancyEqMixin):
+ """
+ """
+ implements(IPopulation)
+
+ compareAttributes = ['_mu', '_sigma']
+
+ def __init__(self, mu, sigma):
+ self._mu = mu
+ self._sigma = sigma
+
+
+ def sample(self):
+ return random.lognormvariate(self._mu, self._sigma)
+
+
+
+class NearFutureDistribution(object, FancyEqMixin):
+ compareAttributes = ['_offset']
+
+ def __init__(self):
+ self._offset = LogNormalDistribution(7, 0.8)
+
+
+ def sample(self):
+ return time.time() + self._offset.sample()
+
+
+
+class NormalDistribution(object, FancyEqMixin):
+ compareAttributes = ['_mu', '_sigma']
+
+ def __init__(self, mu, sigma):
+ self._mu = mu
+ self._sigma = sigma
+
+
+ def sample(self):
+ return random.normalvariate(self._mu, self._sigma)
+
+
+
+class UniformIntegerDistribution(object, FancyEqMixin):
+ compareAttributes = ['_min', '_max']
+
+ def __init__(self, min, max):
+ self._min = min
+ self._max = max
+
+
+ def sample(self):
+ return int(random.uniform(self._min, self._max))
+
+
+NUM_WEEKDAYS = 7
+
+class WorkDistribution(object, FancyEqMixin):
+ compareAttributes = ["_daysOfWeek", "_beginHour", "_endHour"]
+
+ _weekdayNames = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]
+
+ now = staticmethod(datetime.datetime.now)
+
+ def __init__(self, daysOfWeek=["mon", "tue", "wed", "thu", "fri"], beginHour=8, endHour=17, tzname="UTC"):
+ self._daysOfWeek = [self._weekdayNames.index(day) for day in daysOfWeek]
+ self._beginHour = beginHour
+ self._endHour = endHour
+ self._tzinfo = pytz.timezone(tzname)
+ self._helperDistribution = NormalDistribution(
+ # Mean 6 workdays in the future
+ 60 * 60 * 8 * 6,
+ # Standard deviation of 4 workdays
+ 60 * 60 * 8 * 4)
+
+
+ def astimestamp(self, dt):
+ return time.mktime(dt.timetuple())
+
+
+ def _findWorkAfter(self, when):
+ """
+ Return a two-tuple of the start and end of work hours following
+ C{when}. If C{when} falls within work hours, then the start time will
+ be equal to when.
+ """
+ # Find a workday that follows the timestamp
+ weekday = when.weekday()
+ for i in range(NUM_WEEKDAYS):
+ day = when + datetime.timedelta(days=i)
+ if (weekday + i) % NUM_WEEKDAYS in self._daysOfWeek:
+ # Joy, a day on which work might occur. Find the first hour on
+ # this day when work may start.
+ begin = day.replace(
+ hour=self._beginHour, minute=0, second=0, microsecond=0)
+ end = begin.replace(hour=self._endHour)
+ if end > when:
+ return begin, end
+
+
+ def sample(self):
+ offset = datetime.timedelta(seconds=self._helperDistribution.sample())
+ beginning = self.now(self._tzinfo)
+ while offset:
+ start, end = self._findWorkAfter(beginning)
+ if end - start > offset:
+ result = start + offset
+ return self.astimestamp(
+ result.replace(
+ minute=result.minute // 15 * 15,
+ second=0, microsecond=0))
+ offset -= (end - start)
+ beginning = end
Modified: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/test_stats.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/test_stats.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/test_stats.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -14,10 +14,15 @@
# limitations under the License.
##
-from unittest import TestCase
+import pytz
+from datetime import datetime
-from stats import SQLDuration
+from twisted.trial.unittest import TestCase
+from stats import (
+ SQLDuration, LogNormalDistribution, UniformDiscreteDistribution,
+ UniformIntegerDistribution, WorkDistribution, quantize)
+
class SQLDurationTests(TestCase):
def setUp(self):
self.stat = SQLDuration('foo')
@@ -40,3 +45,87 @@
self.stat.normalize('SELECT foo FROM bar WHERE True'),
'SELECT foo FROM bar WHERE ?')
+
+
+class DistributionTests(TestCase):
+ def test_lognormal(self):
+ dist = LogNormalDistribution(1, 1)
+ for i in range(100):
+ value = dist.sample()
+ self.assertIsInstance(value, float)
+ self.assertTrue(value >= 0.0, "negative value %r" % (value,))
+ self.assertTrue(value <= 1000, "implausibly high value %r" % (value,))
+
+
+ def test_uniformdiscrete(self):
+ population = [1, 5, 6, 9]
+ counts = dict.fromkeys(population, 0)
+ dist = UniformDiscreteDistribution(population)
+ for i in range(len(population) * 10):
+ counts[dist.sample()] += 1
+ self.assertEqual(dict.fromkeys(population, 10), counts)
+
+
+ def test_workdistribution(self):
+ tzname = "US/Eastern"
+ tzinfo = pytz.timezone(tzname)
+ dist = WorkDistribution(["mon", "wed", "thu", "sat"], 10, 20, tzname)
+ dist._helperDistribution = UniformDiscreteDistribution([35 * 60 * 60 + 30 * 60])
+ dist.now = lambda tz=None: datetime(2011, 5, 29, 18, 5, 36, tzinfo=tz)
+ value = dist.sample()
+ self.assertEqual(
+ # Move past three workdays - monday, wednesday, thursday - using 30
+ # of the hours, and then five and a half hours into the fourth
+ # workday, saturday. Workday starts at 10am, so the sample value
+ # is 3:30pm, ie 1530 hours.
+ datetime(2011, 6, 4, 15, 30, 0, tzinfo=tzinfo),
+ datetime.fromtimestamp(value, tzinfo))
+
+
+ def test_uniform(self):
+ dist = UniformIntegerDistribution(-5, 10)
+ for i in range(100):
+ value = dist.sample()
+ self.assertTrue(-5 <= value < 10)
+ self.assertIsInstance(value, int)
+
+
+
+class QuantizationTests(TestCase):
+ """
+ Tests for L{quantize} which constructs discrete datasets of
+ dynamic quantization from continuous datasets.
+ """
+ skip = "nothing implemented yet, maybe not necessary"
+
+ def test_one(self):
+ """
+ A single data point is put into a bucket equal to its value and returned.
+ """
+ dataset = [5.0]
+ expected = [(5.0, [5.0])]
+ self.assertEqual(quantize(dataset), expected)
+
+
+ def test_two(self):
+ """
+ Each of two values are put into buckets the size of the
+ standard deviation of the sample.
+ """
+ dataset = [2.0, 5.0]
+ expected = [(1.5, [2.0]), (4.5, [5.0])]
+ self.assertEqual(quantize(dataset), expected)
+
+
+ def xtest_three(self):
+ """
+ If two out of three values fall within one bucket defined by
+ the standard deviation of the sample, that bucket is split in
+ half so each bucket has one value.
+ """
+
+
+ def xtest_alpha(self):
+ """
+ This exercises one simple case of L{quantize} with a small amount of data.
+ """
Deleted: CalendarServer/branches/users/cdaboo/timezones/contrib/performance/todo
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/performance/todo 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/performance/todo 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,4 +0,0 @@
-description of each benchmark on the website
-investigate the performance regression near r6270 to see if it is real
-benchmarks for larger calendars (more events)
-extend report to know about linear scaling, quadratic scaling, etc perhaps
Copied: CalendarServer/branches/users/cdaboo/timezones/contrib/tools/backup (from rev 7695, CalendarServer/trunk/contrib/tools/backup)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/tools/backup (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/tools/backup 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+
+##
+# Copyright (c) 2011 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 getopt import getopt, GetoptError
+import os
+import subprocess
+import sys
+import tarfile
+from tempfile import mkstemp
+from shutil import rmtree
+
+from twistedcaldav.config import config
+from calendarserver.tools.util import loadConfig
+
+USERNAME = "caldav"
+DATABASENAME = "caldav"
+DUMPFILENAME = "db_backup"
+PLISTPATH = "/etc/caldavd/caldavd.plist"
+PLISTNAME = "caldavd.plist"
+
+PSQL = "/usr/bin/psql"
+PGDUMP = "/usr/bin/pg_dump"
+
+def usage(e=None):
+ name = os.path.basename(sys.argv[0])
+ print "usage: %s [options] command backup-file" % (name,)
+ print ""
+ print " Backup or restore calendar and addressbook data"
+ print ""
+ print "options:"
+ print " -f --config <path>: Specify caldavd.plist configuration path"
+ print " -h --help: print this help and exit"
+ print " -v --verbose: print additional information"
+ print ""
+ print "commands:"
+ print " backup: create backup-file"
+ print " restore: restore from backup-file"
+ print ""
+
+ if e:
+ sys.stderr.write("%s\n" % (e,))
+ sys.exit(64)
+ else:
+ sys.exit(0)
+
+
+def dumpData(dumpFile, verbose=False):
+ """
+ Use pg_dump to dump data to dumpFile
+ """
+
+ cmdArgs = [
+ PGDUMP,
+ "--username=%s" % (USERNAME,),
+ "--clean",
+ "--no-privileges",
+ "--file=%s" % (dumpFile,),
+ DATABASENAME
+ ]
+ try:
+ if verbose:
+ print "\nDumping data to %s" % (dumpFile,)
+ print "Executing: %s" % (" ".join(cmdArgs))
+ out = subprocess.check_output(cmdArgs, stderr=subprocess.STDOUT)
+ if verbose:
+ print out
+ except subprocess.CalledProcessError, e:
+ if verbose:
+ print e.output
+ raise BackupError(
+ "%s failed:\n%s (exit code = %d)" %
+ (PGDUMP, e.output, e.returncode)
+ )
+
+
+def loadData(dumpFile, verbose=False):
+ """
+ Use psql to load data from dumpFile
+ """
+
+ cmdArgs = [
+ PSQL,
+ "--username=%s" % (USERNAME,),
+ "--file=%s" % (dumpFile,)
+ ]
+ try:
+ if verbose:
+ print "\nLoading data from %s" % (dumpFile,)
+ print "Executing: %s" % (" ".join(cmdArgs))
+ out = subprocess.check_output(cmdArgs, stderr=subprocess.STDOUT)
+ if verbose:
+ print out
+ except subprocess.CalledProcessError, e:
+ if verbose:
+ print e.output
+ raise BackupError(
+ "%s failed:\n%s (exit code = %d)" %
+ (PGDUMP, e.output, e.returncode)
+ )
+
+
+class BackupError(Exception):
+ pass
+
+
+def error(s):
+ sys.stderr.write("%s\n" % (s,))
+ sys.exit(1)
+
+
+def main():
+ try:
+ (optargs, args) = getopt(
+ sys.argv[1:], "f:hv", [
+ "config=",
+ "help",
+ "verbose",
+ ],
+ )
+ except GetoptError, e:
+ usage(e)
+
+ verbose = False
+ configFileName = None
+
+ for opt, arg in optargs:
+ if opt in ("-h", "--help"):
+ usage()
+ elif opt in ("-f", "--config"):
+ configFileName = arg
+ elif opt in ("-v", "--verbose"):
+ verbose = True
+ else:
+ raise NotImplementedError(opt)
+
+ if len(args) != 2:
+ usage("Must specify a command and a backup-file name.")
+
+ command = args[0]
+ filename = args[1]
+
+ loadConfig(configFileName)
+
+ serverRoot = config.ServerRoot
+ dataRoot = config.DataRoot
+ docRoot = config.DocumentRoot
+
+ if command == "backup":
+
+ fd, tmpPath = mkstemp(suffix=".dbdump")
+
+ try:
+ dumpData(tmpPath, verbose=verbose)
+
+ if verbose:
+ print "Creating %s" % (filename,)
+ tar = tarfile.open(filename, "w:gz")
+ if verbose:
+ print "Adding %s" % (dataRoot,)
+ tar.add(dataRoot, "Data")
+ if verbose:
+ print "Adding %s" % (docRoot,)
+ tar.add(docRoot, "Documents")
+ if verbose:
+ print "Adding %s" % (tmpPath,)
+ tar.add(tmpPath, DUMPFILENAME)
+ if verbose:
+ print "Adding %s" % (PLISTPATH,)
+ tar.add(PLISTPATH, PLISTNAME)
+ tar.close()
+
+ if verbose:
+ print "Removing %s" % (tmpPath,)
+ os.remove(tmpPath)
+
+ if verbose:
+ print "Done"
+ except BackupError, e:
+ error("Failed to dump database; error: %s" % (e,))
+
+ elif command == "restore":
+
+ try:
+ tar = tarfile.open(filename, "r:gz")
+ os.chdir(serverRoot)
+
+ if os.path.exists(dataRoot):
+ if verbose:
+ print "Removing old DataRoot: %s" % (dataRoot,)
+ rmtree(dataRoot)
+
+ if os.path.exists(docRoot):
+ if verbose:
+ print "Removing old DocumentRoot: %s" % (docRoot,)
+ rmtree(docRoot)
+
+ if verbose:
+ print "Extracting from backup file: %s" % (filename,)
+ tar.extractall()
+
+ loadData(DUMPFILENAME, verbose=verbose)
+
+ if verbose:
+ print "Cleaning up database dump file: %s" % (DUMPFILENAME,)
+ os.remove(DUMPFILENAME)
+
+ except BackupError, e:
+ error("Failed to dump database; error: %s" % (e,))
+ raise
+
+ else:
+ error("Unknown command '%s'" % (command,))
+
+if __name__ == "__main__":
+ main()
Copied: CalendarServer/branches/users/cdaboo/timezones/contrib/tools/fakecalendardata.py (from rev 7695, CalendarServer/trunk/contrib/tools/fakecalendardata.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/contrib/tools/fakecalendardata.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/contrib/tools/fakecalendardata.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,212 @@
+#!/usr/bin/env python
+##
+# Copyright (c) 2011 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 datetime
+import getopt
+import os
+import random
+import sys
+import uuid
+
+outputFile = None
+fileCount = 0
+lastWeek = None
+
+calendar_template = """BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+PRODID:-//Apple Inc.//iCal 4.0.3//EN
+BEGIN:VTIMEZONE
+TZID:America/Los_Angeles
+BEGIN:STANDARD
+DTSTART:20071104T020000
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+TZNAME:PST
+TZOFFSETFROM:-0700
+TZOFFSETTO:-0800
+END:STANDARD
+BEGIN:DAYLIGHT
+DTSTART:20070311T020000
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+TZNAME:PDT
+TZOFFSETFROM:-0800
+TZOFFSETTO:-0700
+END:DAYLIGHT
+END:VTIMEZONE
+%(VEVENTS)s\
+END:VCALENDAR
+"""
+
+vevent_template = """\
+BEGIN:VEVENT
+UID:%(UID)s
+DTSTART;TZID=America/Los_Angeles:%(START)s
+DURATION:P1H
+%(RRULE)s\
+CREATED:20100729T193912Z
+DTSTAMP:20100729T195557Z
+%(ORGANIZER)s\
+%(ATTENDEES)s\
+SEQUENCE:0
+SUMMARY:%(SUMMARY)s
+TRANSP:OPAQUE
+END:VEVENT
+"""
+
+attendee_template = """\
+ATTENDEE;CN=User %(SEQUENCE)02d;CUTYPE=INDIVIDUAL;EMAIL=user%(SEQUENCE)02d at example.com;PARTSTAT=NE
+ EDS-ACTION;ROLE=REQ-PARTICIPANT;RSVP=TRUE:urn:uuid:user%(SEQUENCE)02d
+"""
+
+organizer_template = """\
+ORGANIZER;CN=User %(SEQUENCE)02d;EMAIL=user%(SEQUENCE)02d at example.com:urn:uuid:user%(SEQUENCE)02d
+ATTENDEE;CN=User %(SEQUENCE)02d;EMAIL=user%(SEQUENCE)02d at example.com;PARTSTAT=ACCEPTE
+ D:urn:uuid:user%(SEQUENCE)02d
+"""
+
+summary_template = "Event %d"
+rrules_template = (
+ "RRULE:FREQ=DAILY;COUNT=5\n",
+ "RRULE:FREQ=DAILY;BYDAY=MO,TU,WE,TH,FR\n",
+ "RRULE:FREQ=YEARLY\n",
+)
+
+def makeVEVENT(recurring, atendees, date, hour, count):
+
+ subs = {
+ "UID": str(uuid.uuid4()),
+ "START" : "",
+ "RRULE" : "",
+ "ORGANIZER" : "",
+ "ATTENDEES" : "",
+ "SUMMARY" : summary_template % (count,)
+ }
+
+ if recurring:
+ subs["RRULE"] = random.choice(rrules_template)
+
+ if attendees:
+ subs["ORGANIZER"] = organizer_template % {"SEQUENCE": 1}
+ for ctr in range(2, random.randint(2, 10)):
+ subs["ATTENDEES"] += attendee_template % {"SEQUENCE": ctr}
+
+ subs["START"] = "%04d%02d%02dT%02d0000" % (date.year, date.month, date.day, hour)
+
+ return vevent_template % subs
+
+def argPath(path):
+ fpath = os.path.expanduser(path)
+ if not fpath.startswith("/"):
+ fpath = os.path.join(pwd, fpath)
+ return fpath
+
+def usage(error_msg=None):
+ if error_msg:
+ print error_msg
+
+ print """Usage: fakecalendardata [options]
+Options:
+ -h Print this help and exit
+ -a Percentage of events that should include attendees
+ -c Total number of events to generate
+ -d Directory to store separate .ics files into
+ -r Percentage of recurring events to create
+ -p Numbers of years in the past to start at
+ -f Number of years into the future to end at
+
+Arguments: None
+
+Description:
+This utility will generate fake iCalendar data either into a single .ics
+file or into multiple .ics files.
+"""
+
+ if error_msg:
+ raise ValueError(error_msg)
+ else:
+ sys.exit(0)
+
+if __name__ == "__main__":
+
+ outputDir = None
+ totalCount = 10
+ percentRecurring = 20
+ percentWithAttendees = 10
+ yearsPast = 2
+ yearsFuture = 1
+
+ options, args = getopt.getopt(sys.argv[1:], "a:c:f:hd:p:r:", [])
+
+ for option, value in options:
+ if option == "-h":
+ usage()
+ elif option == "-a":
+ percentWithAttendees = int(value)
+ elif option == "-c":
+ totalCount = int(value)
+ elif option == "-d":
+ outputDir = argPath(value)
+ elif option == "-f":
+ yearsFuture = int(value)
+ elif option == "-p":
+ yearsPast = int(value)
+ elif option == "-r":
+ percentRecurring = int(value)
+ else:
+ usage("Unrecognized option: %s" % (option,))
+
+ if outputDir and not os.path.isdir(outputDir):
+ usage("Must specify a valid output directory.")
+
+ # Process arguments
+ if len(args) != 0:
+ usage("No arguments allowed")
+
+ pwd = os.getcwd()
+
+ totalRecurring = (totalCount * percentRecurring) / 100
+ totalRecurringWithAttendees = (totalRecurring * percentWithAttendees) / 100
+ totalRecurringWithoutAttendees = totalRecurring - totalRecurringWithAttendees
+
+ totalNonRecurring = totalCount - totalRecurring
+ totalNonRecurringWithAttendees = (totalNonRecurring * percentWithAttendees) / 100
+ totalNonRecurringWithoutAttendees = totalNonRecurring - totalNonRecurringWithAttendees
+
+ eventTypes = []
+ eventTypes.extend([(True, True) for _ignore in range(totalRecurringWithAttendees)])
+ eventTypes.extend([(True, False) for _ignore in range(totalRecurringWithoutAttendees)])
+ eventTypes.extend([(False, True) for _ignore in range(totalNonRecurringWithAttendees)])
+ eventTypes.extend([(False, False) for _ignore in range(totalNonRecurringWithoutAttendees)])
+ random.shuffle(eventTypes)
+
+ totalYears = yearsPast + yearsFuture
+ totalDays = totalYears * 365
+
+ startDate = datetime.date.today() - datetime.timedelta(days=yearsPast*365)
+
+ for i in range(len(eventTypes)):
+ eventTypes[i] += (
+ startDate + datetime.timedelta(days=random.randint(0, totalDays)),
+ random.randint(8, 18),
+ )
+
+ vevents = []
+ for count, (recurring, attendees, date, hour) in enumerate(eventTypes):
+ #print recurring, attendees, date, hour
+ vevents.append(makeVEVENT(recurring, attendees, date, hour, count+1))
+
+ print calendar_template % {"VEVENTS" : "".join(vevents)}
Modified: CalendarServer/branches/users/cdaboo/timezones/doc/calendarserver_monitor_notifications.8
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/doc/calendarserver_monitor_notifications.8 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/doc/calendarserver_monitor_notifications.8 2011-06-30 20:44:13 UTC (rev 7697)
@@ -25,6 +25,7 @@
.Op Fl -admin Ar username
.Op Fl -config Ar file
.Op Fl -host Ar hostname
+.Op Fl -node Ar pubsub-node-name
.Op Fl -port Ar port-number
.Op Fl -ssl
.Op Fl -verbose
@@ -44,6 +45,8 @@
Use the Calendar Server configuration specified in the given file. Defaults to /etc/caldavd/caldavd.plist.
.It Fl H, -host Ar HOSTNAME
Connect to the specified calendar server. If not passed on the command line, the host name is looked up in the configuration file.
+.It Fl n, -node Ar NODENAME
+Bypass the auto-discovery of pubsub nodes and specify one explicitly. Useful on calendar servers which don't support the push-transports DAV property. When using this option, the host and port options instead refer to the XMPP server host and port numbers.
.It Fl p, -port Ar NUMBER
Connect to the specified port number. If not passed on the command line, the port number is looked up in the configuration file.
.It Fl s, -ssl
Deleted: CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch
===================================================================
--- CalendarServer/trunk/lib-patches/cx_Oracle/bytes-per-nclob-character.patch 2011-06-30 16:37:39 UTC (rev 7695)
+++ CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,17 +0,0 @@
-Index: ExternalLobVar.c
-===================================================================
---- ExternalLobVar.c (revision 355)
-+++ ExternalLobVar.c (working copy)
-@@ -259,10 +259,9 @@
- amount = 1;
- }
- length = amount;
-- if (var->lobVar->type == &vt_CLOB)
-+ if ((var->lobVar->type == &vt_CLOB) ||
-+ (var->lobVar->type == &vt_NCLOB))
- bufferSize = amount * var->lobVar->environment->maxBytesPerCharacter;
-- else if (var->lobVar->type == &vt_NCLOB)
-- bufferSize = amount * 2;
- else bufferSize = amount;
-
- // create a string for retrieving the value
Copied: CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch (from rev 7695, CalendarServer/trunk/lib-patches/cx_Oracle/bytes-per-nclob-character.patch)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/lib-patches/cx_Oracle/bytes-per-nclob-character.patch 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,17 @@
+Index: ExternalLobVar.c
+===================================================================
+--- ExternalLobVar.c (revision 355)
++++ ExternalLobVar.c (working copy)
+@@ -259,10 +259,9 @@
+ amount = 1;
+ }
+ length = amount;
+- if (var->lobVar->type == &vt_CLOB)
++ if ((var->lobVar->type == &vt_CLOB) ||
++ (var->lobVar->type == &vt_NCLOB))
+ bufferSize = amount * var->lobVar->environment->maxBytesPerCharacter;
+- else if (var->lobVar->type == &vt_NCLOB)
+- bufferSize = amount * 2;
+ else bufferSize = amount;
+
+ // create a string for retrieving the value
Modified: CalendarServer/branches/users/cdaboo/timezones/pyflakes
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/pyflakes 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/pyflakes 2011-06-30 20:44:13 UTC (rev 7697)
@@ -9,7 +9,7 @@
export PYTHONPATH="${flakes}:${PYTHONPATH:-}";
if [ $# -eq 0 ]; then
- set - calendarserver twext twisted twistedcaldav txdav;
+ set - calendarserver twext twisted twistedcaldav txdav contrib;
fi;
tmp="$(mktemp -t pyflakes)";
Modified: CalendarServer/branches/users/cdaboo/timezones/run
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/run 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/run 2011-06-30 20:44:13 UTC (rev 7697)
@@ -44,7 +44,7 @@
echo;
fi;
- echo "Usage: ${program} [-hvgsfnpdkrR] [-K key] [-iI dst] [-t type] [-S statsdirectory] [-P plugin]";
+ echo "Usage: ${program} [-hvgsfnpdkrR] [-K key] [-iIb dst] [-t type] [-S statsdirectory] [-P plugin]";
echo "Options:";
echo " -h Print this help and exit";
echo " -v Be verbose";
@@ -59,6 +59,7 @@
echo " -K Print value of configuration key and exit";
echo " -i Perform a system install into dst; implies -s";
echo " -I Perform a home install into dst; implies -s";
+ echo " -b Perform a bundled install (include all dependencies) into dst; implies -s";
echo " -t Select the server process type (Master, Slave or Combined) [${service_type}]";
echo " -S Write a pstats object for each process to the given directory when the server is stopped.";
echo " -P Select the twistd plugin name [${plugin_name}]";
@@ -74,7 +75,7 @@
# functions in build.sh.
parse_options () {
OPTIND=1;
- while getopts "ahvgsfnpdkrK:i:I:t:S:P:R:" option; do
+ while getopts "ahvgsfnpdkrK:i:I:b:t:S:P:R:" option; do
case "${option}" in
'?') usage; ;;
'h') usage -; exit 0; ;;
@@ -90,9 +91,32 @@
'S') profile="-p ${OPTARG}"; ;;
'g') do_get="true" ; do_setup="false"; do_run="false"; ;;
's') do_get="true" ; do_setup="true" ; do_run="false"; ;;
- 'p') do_get="false"; do_setup="false"; do_run="false"; print_path="true"; ;;
- 'i') do_get="true" ; do_setup="true" ; do_run="false"; install="${OPTARG}"; install_flag="--root="; ;;
- 'I') do_get="true" ; do_setup="true" ; do_run="false"; install="${wd}/build/dst"; install_flag="--root="; install_home="${OPTARG}"; ;;
+ 'p')
+ do_get="false"; do_setup="false"; do_run="false"; print_path="true";
+ ;;
+ 'i')
+ do_get="true";
+ do_setup="true";
+ do_run="false";
+ install="${OPTARG}";
+ install_flag="--root=";
+ ;;
+ 'I')
+ do_get="true";
+ do_setup="true";
+ do_run="false";
+ install="${wd}/build/dst";
+ install_flag="--root=";
+ install_home="${OPTARG}";
+ ;;
+ 'b')
+ do_bundle="true";
+ do_get="true";
+ do_setup="true";
+ do_run="false";
+ install="${OPTARG}";
+ install_flag="--root=";
+ ;;
'n') do_get="false" ; do_setup="false"; ;;
esac;
done;
@@ -210,6 +234,10 @@
do_home_install;
fi;
+ if "${do_bundle}"; then
+ write_environment;
+ fi;
+
# Finally, run the server.
run;
}
Modified: CalendarServer/branches/users/cdaboo/timezones/setup.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/setup.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/setup.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -29,11 +29,16 @@
]
for root, dirs, files in os.walk("."):
- for exclude in (
+ excludes = [
".svn",
"_trial_temp",
"build",
- ):
+ ]
+
+ if root == ".":
+ excludes.append("data")
+
+ for exclude in excludes:
if exclude in dirs:
dirs.remove(exclude)
@@ -86,7 +91,7 @@
# Run setup
#
-if __name__ == "__main__":
+def doSetup():
from distutils.core import setup
dist = setup(
@@ -134,6 +139,8 @@
if "install" in dist.commands:
install_obj = dist.command_obj["install"]
+ if install_obj.root is None:
+ return
install_scripts = os.path.normpath(install_obj.install_scripts)
install_lib = os.path.normpath(install_obj.install_lib)
root = os.path.normpath(install_obj.root)
@@ -182,3 +189,10 @@
newScript = open(scriptPath, "w")
newScript.write("\n".join(script))
newScript.close()
+
+
+
+if __name__ == "__main__":
+ doSetup()
+
+
Copied: CalendarServer/branches/users/cdaboo/timezones/sim (from rev 7695, CalendarServer/trunk/sim)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/sim (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/sim 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+##
+# Copyright (c) 2005-2011 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.
+##
+
+set -e;
+set -u;
+
+wd="$(cd "$(dirname "$0")" && pwd -L)";
+
+export PYTHONPATH="$("${wd}/run" -p)";
+
+exec "${wd}/contrib/performance/sim" "$@";
Property changes on: CalendarServer/branches/users/cdaboo/timezones/support
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/support/build.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/support/build.sh 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/support/build.sh 2011-06-30 20:44:13 UTC (rev 7697)
@@ -41,6 +41,7 @@
do_get="true";
do_setup="true";
do_run="true";
+ do_bundle="false";
force_setup="false";
disable_setup="false";
print_path="false";
@@ -340,8 +341,10 @@
svn checkout -r "${revision}" "${uri}@${revision}" "${path}";
}
- if [ "${revision}" != "HEAD" ] && [ -n "${cache_deps}" ] && [ -n "${hash}" ]; then
- local cache_file="${cache_deps}/${name}-$(echo "${uri}" | hash)@r${revision}.tgz";
+ if [ "${revision}" != "HEAD" ] && [ -n "${cache_deps}" ] \
+ && [ -n "${hash}" ]; then
+ local cacheid="${name}-$(echo "${uri}" | hash)";
+ local cache_file="${cache_deps}/${cacheid}@r${revision}.tgz";
mkdir -p "${cache_deps}";
@@ -372,10 +375,12 @@
if "${do_setup}"; then
echo "Building ${name}...";
cd "${path}";
- if ! "${python}" ./setup.py -q build --build-lib "build/${py_platform_libdir}" "$@"; then
+ if ! "${python}" ./setup.py -q build \
+ --build-lib "build/${py_platform_libdir}" "$@"; then
if "${optional}"; then
echo "WARNING: ${name} failed to build.";
- echo "WARNING: ${name} is not required to run the server; continuing without it.";
+ echo "WARNING: ${name} is not required to run the server;"\
+ "continuing without it.";
else
return $?;
fi;
@@ -394,7 +399,13 @@
echo "";
echo "Installing ${name}...";
cd "${path}";
- "${python}" ./setup.py install "${install_flag}${install}";
+ if "${do_bundle}"; then
+ # Since we've built our own Python, an option-free installation is the
+ # best bet.
+ "${python}" ./setup.py install;
+ else
+ "${python}" ./setup.py install "${install_flag}${install}";
+ fi;
cd /;
fi;
}
@@ -403,16 +414,15 @@
# Declare a dependency on a Python project.
py_dependency () {
local optional="false"; # Is this dependency optional?
- local override="false"; # Do I need to get this dependency even if
- # the system already has it?
- local inplace=""; # Do development in-place; don't run
- # setup.py to build, and instead add the
- # source directory plus the given relative
- # path directly to sys.path. twisted and
- # vobject are developed often enough that
- # this is convenient.
- local skip_egg="false"; # Skip even the 'egg_info' step, because
- # nothing needs to be built.
+ local override="false"; # Do I need to get this dependency even if the system
+ # already has it?
+ local inplace=""; # Do development in-place; don't run setup.py to
+ # build, and instead add the source directory plus the
+ # given relative path directly to sys.path. twisted
+ # and vobject are developed often enough that this is
+ # convenient.
+ local skip_egg="false"; # Skip even the 'egg_info' step, because nothing needs
+ # to be built.
local revision="0"; # Revision (if svn)
local get_type="www"; # Protocol to use
local version=""; # Minimum version required
@@ -441,7 +451,8 @@
# args
local name="$1"; shift; # the name of the package (for display)
local module="$1"; shift; # the name of the python module.
- local distribution="$1"; shift; # the name of the directory to put the distribution into.
+ local distribution="$1"; shift; # the name of the directory to put the
+ # distribution into.
local get_uri="$1"; shift; # what URL should be fetched?
local srcdir="${top}/${distribution}"
@@ -455,7 +466,7 @@
if "${do_setup}" && "${override}" && ! "${skip_egg}"; then
echo;
if py_have_module setuptools; then
- echo "Building ${name}... [overrides system, building egg-info metadata only]";
+ echo "Building ${name}... [overrides system, building egg-info only]";
cd "${srcdir}";
"${python}" ./setup.py -q egg_info 2>&1 | (
grep -i -v 'Unrecognized .svn/entries' || true);
@@ -489,6 +500,8 @@
fi;
}
+# Run 'make' with the given command line, prepending a -j option appropriate to
+# the number of CPUs on the current machine, if that can be determined.
jmake () {
case "$(uname -s)" in
Darwin|Linux)
@@ -525,25 +538,48 @@
# Extra arguments are processed below, as arguments to './configure'.
- srcdir="${top}/${path}";
+ if "${do_bundle}"; then
+ local dstroot="${install}";
+ srcdir="${install}/src/${path}";
+ else
+ srcdir="${top}/${path}";
+ local dstroot="${srcdir}/_root";
+ fi;
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:-}";
+ export CPPFLAGS="-I${dstroot}/include ${CPPFLAGS:-} ";
+ export LDFLAGS="-L${dstroot}/lib ${LDFLAGS:-} ";
+ export DYLD_LIBRARY_PATH="${dstroot}/lib:${DYLD_LIBRARY_PATH:-}";
+
if "${do_setup}" && (
- "${force_setup}" || [ ! -d "${srcdir}/_root" ]); then
+ "${force_setup}" || "${do_bundle}" || [ ! -d "${dstroot}" ]); then
echo "Building ${name}...";
cd "${srcdir}";
- ./configure --prefix="${srcdir}/_root" "$@";
+ ./configure --prefix="${dstroot}" "$@";
jmake;
jmake install;
fi;
+}
- export PATH="${PATH}:${srcdir}/_root/bin";
- export C_INCLUDE_PATH="${C_INCLUDE_PATH:-}:${srcdir}/_root/include";
- export LD_LIBRARY_PATH="${LD_LIBRARY_PATH:-}:${srcdir}/_root/lib";
- export CPPFLAGS="${CPPFLAGS:-} -I${srcdir}/_root/include";
- export LDFLAGS="${LDFLAGS:-} -L${srcdir}/_root/lib";
- export DYLD_LIBRARY_PATH="${DYLD_LIBRARY_PATH:-}:${srcdir}/_root/lib";
+# Used only when bundling: write out, into the bundle, an 'environment.sh' file
+# that contains all the environment variables necessary to invoke commands in
+# the deployed bundle.
+
+write_environment () {
+ local dstroot="${install}";
+ cat > "${dstroot}/environment.sh" << __EOF__
+export PATH="${dstroot}/bin:\${PATH}";
+export C_INCLUDE_PATH="${dstroot}/include:\${C_INCLUDE_PATH:-}";
+export LD_LIBRARY_PATH="${dstroot}/lib:\${LD_LIBRARY_PATH:-}";
+export CPPFLAGS="-I${dstroot}/include \${CPPFLAGS:-} ";
+export LDFLAGS="-L${dstroot}/lib \${LDFLAGS:-} ";
+export DYLD_LIBRARY_PATH="${dstroot}/lib:\${DYLD_LIBRARY_PATH:-}";
+__EOF__
}
@@ -560,6 +596,25 @@
# Dependencies compiled from C source code
#
+
+ if "${do_bundle}"; then
+ # First a bit of bootstrapping: fill out the standard directory structure.
+ for topdir in bin lib include share src; do
+ mkdir -p "${install}/${topdir}";
+ done;
+
+ # Normally we depend on the system Python, but a bundle install should be as
+ # self-contained as possible.
+ local pyfn="Python-2.7.1";
+ c_dependency -m "aa27bc25725137ba155910bd8e5ddc4f" \
+ "Python" "${pyfn}" \
+ "http://www.python.org/ftp/python/2.7.1/${pyfn}.tar.bz2" \
+ --enable-shared;
+ # Be sure to use the Python we just built.
+ export PYTHON="$(type -p python)";
+ init_py;
+ fi;
+
if ! type memcached > /dev/null 2>&1; then
local le="libevent-1.4.13-stable";
local mc="memcached-1.4.5";
@@ -568,8 +623,7 @@
"http://monkey.org/~provos/${le}.tar.gz";
c_dependency -m "583441a25f937360624024f2881e5ea8" \
"memcached" "${mc}" \
- "http://memcached.googlecode.com/files/${mc}.tar.gz" \
- --enable-threads --with-libevent="${top}/${le}/_root";
+ "http://memcached.googlecode.com/files/${mc}.tar.gz";
fi;
if ! type postgres > /dev/null 2>&1; then
@@ -599,7 +653,13 @@
# Sourceforge mirror hostname.
local sf="superb-sea2.dl.sourceforge.net";
+ local st="setuptools-0.6c11";
+ local pypi="http://pypi.python.org/packages/source";
+ py_dependency -m "7df2a529a074f613b509fb44feefe74e" \
+ "setuptools" "setuptools" "${st}" \
+ "$pypi/s/setuptools/setuptools-0.6c11.tar.gz";
+
local zi="zope.interface-3.3.0";
py_dependency -m "93668855e37b4691c5c956665c33392c" \
"Zope Interface" "zope.interface" "${zi}" \
@@ -614,7 +674,7 @@
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";
+ "${pypi}/p/pyOpenSSL/${po}.tar.gz";
if type krb5-config > /dev/null 2>&1; then
py_dependency -r 4241 \
@@ -639,21 +699,22 @@
"http://${sf}/project/cx-oracle/5.1/${cx}.tar.gz";
fi;
- if [ "${py_version}" != "${py_version##2.5}" ] && ! py_have_module select26; then
+ if [ "${py_version}" != "${py_version##2.5}" ] && \
+ ! py_have_module select26; then
local s26="select26-0.1a3";
py_dependency -m "01b8929e7cfc4a8deb777b92e3115c15" \
"select26" "select26" "${s26}" \
- "http://pypi.python.org/packages/source/s/select26/${s26}.tar.gz";
+ "${pypi}/s/select26/${s26}.tar.gz";
fi;
local pg="PyGreSQL-4.0";
py_dependency -v 4.0 -m "1aca50e59ff4cc56abe9452a9a49c5ff" -o \
"PyGreSQL" "pgdb" "${pg}" \
- "http://pypi.python.org/packages/source/P/PyGreSQL/${pg}.tar.gz";
+ "${pypi}/P/PyGreSQL/${pg}.tar.gz";
- py_dependency -v 10.1 -r 30159 \
+ py_dependency -v 11 -r 31512 \
"Twisted" "twisted" "Twisted" \
- "svn://svn.twistedmatrix.com/svn/Twisted/tags/releases/twisted-10.1.0";
+ "svn://svn.twistedmatrix.com/svn/Twisted/tags/releases/twisted-11.0.0";
local du="python-dateutil-1.5";
py_dependency -m "35f3732db3f2cc4afdc68a8533b60a52" \
@@ -663,7 +724,7 @@
local ld="python-ldap-2.3.13";
py_dependency -v "2.3.13" -m "895223d32fa10bbc29aa349bfad59175" \
"python-ldap" "python-ldap" "${ld}" \
- "http://pypi.python.org/packages/source/p/python-ldap/${ld}.tar.gz";
+ "${pypi}/p/python-ldap/${ld}.tar.gz";
# XXX actually vObject should be imported in-place.
py_dependency -fe -i "" -r 219 \
@@ -671,29 +732,37 @@
"http://svn.osafoundation.org/vobject/trunk";
# XXX actually PyCalendar should be imported in-place.
- py_dependency -fe -i "src" -r 161 \
+ py_dependency -fe -i "src" -r 169 \
"pycalendar" "pycalendar" "pycalendar" \
- "http://svn.mulberrymail.com/repos/PyCalendar/branches/server-stable";
+ "http://svn.mulberrymail.com/repos/PyCalendar/branches/server";
#
# Tool dependencies. The code itself doesn't depend on these, but
# they are useful to developers.
#
- py_dependency -v 0.1.2 -m "aa9852ad81822723adcd9f96838de14e" \
+ py_dependency -o -v 0.1.2 -m "aa9852ad81822723adcd9f96838de14e" \
"SQLParse" "sqlparse" "sqlparse-0.1.2" \
"http://python-sqlparse.googlecode.com/files/sqlparse-0.1.2.tar.gz";
- py_dependency -v 0.4.0 -m "630a72510aae8758f48cf60e4fa17176" \
+ py_dependency -o -v 0.4.0 -m "630a72510aae8758f48cf60e4fa17176" \
"Pyflakes" "pyflakes" "pyflakes-0.4.0" \
- "http://pypi.python.org/packages/source/p/pyflakes/pyflakes-0.4.0.tar.gz";
+ "${pypi}/p/pyflakes/pyflakes-0.4.0.tar.gz";
+
+ py_dependency -o -r HEAD \
+ "CalDAVClientLibrary" "CalDAVClientLibrary" "CalDAVClientLibrary" \
+ "${svn_uri_base}/CalDAVClientLibrary/trunk";
- svn_get "CalDAVTester" "${top}/CalDAVTester" "${svn_uri_base}/CalDAVTester/trunk" HEAD;
+ # Can't add "-v 2011g" to args because the version check expects numbers.
+ py_dependency -o -m "9ffda6e87b5f067a7ca37c54629c9e58" \
+ "pytz" "pytz" "pytz-2011g" \
+ "http://pypi.python.org/packages/source/p/pytz/pytz-2011g.tar.gz";
- svn_get "CalDAVClientLibrary" "${top}/CalDAVClientLibrary" "${svn_uri_base}/CalDAVClientLibrary/trunk" HEAD;
+ svn_get "CalDAVTester" "${top}/CalDAVTester" \
+ "${svn_uri_base}/CalDAVTester/trunk" HEAD;
local pd="pydoctor-0.3";
- py_dependency -m "b000aa1fb458fe25952dadf26049ae68" \
+ py_dependency -o -m "b000aa1fb458fe25952dadf26049ae68" \
"pydoctor" "pydoctor" "${pd}" \
"http://launchpadlibrarian.net/42323121/${pd}.tar.gz";
Property changes on: CalendarServer/branches/users/cdaboo/timezones/support/build.sh
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pods/support/build.sh:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/support/build.sh:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/support/build.sh:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
+ /CalendarServer/branches/config-separation/support/build.sh:4379-4443
/CalendarServer/branches/egg-info-351/support/build.sh:4589-4615
/CalendarServer/branches/generic-sqlstore/support/build.sh:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/support/build.sh:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/support/build.sh:5911-5935
/CalendarServer/branches/new-store/support/build.sh:5594-5934
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/support/build.sh:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/support/build.sh:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/support/build.sh:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/support/build.sh:4465-4957
/CalendarServer/branches/users/cdaboo/pods/support/build.sh:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/support/build.sh:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/support/build.sh:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/support/build.sh:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/support/build.sh:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/support/build.sh:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/support/build.sh:4971-5080
/CalendarServer/branches/users/glyph/dalify/support/build.sh:6932-7023
/CalendarServer/branches/users/glyph/db-reconnect/support/build.sh:6824-6876
/CalendarServer/branches/users/glyph/deploybuild/support/build.sh:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/support/build.sh:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/support/build.sh:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/support/build.sh:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/support/build.sh:6322-6368
/CalendarServer/branches/users/glyph/more-deferreds-7/support/build.sh:6369-6445
/CalendarServer/branches/users/glyph/new-export/support/build.sh:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/support/build.sh:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/support/build.sh:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/support/build.sh:6490-6550
/CalendarServer/branches/users/glyph/sql-store/support/build.sh:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/support/build.sh:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/support/build.sh:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/support/build.sh:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/support/build.sh:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/support/build.sh:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/support/build.sh:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/support/build.sh:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/support/build.sh:4068-4075
/CalendarServer/branches/users/sagen/resources-2/support/build.sh:5084-5093
/CalendarServer/branches/users/wsanchez/transations/support/build.sh:5515-5593
/CalendarServer/trunk/support/build.sh:7443-7695
Modified: CalendarServer/branches/users/cdaboo/timezones/support/py.sh
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/support/py.sh 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/support/py.sh 2011-06-30 20:44:13 UTC (rev 7697)
@@ -32,15 +32,16 @@
if [ -z "${python}" ]; then
return 1;
- fi
+ fi;
+
if ! type "${python}" > /dev/null 2>&1; then
return 1;
- fi
+ fi;
local py_version="$(py_version "${python}")";
if [ "${py_version/./}" -lt "25" ]; then
return 1;
- fi
+ fi;
return 0;
}
@@ -67,9 +68,9 @@
if try_python "${p}"; then
echo "${p}";
return 0;
- fi
- done
- done
+ fi;
+ done;
+ done;
return 1;
}
@@ -192,3 +193,4 @@
}
init_py;
+
Modified: CalendarServer/branches/users/cdaboo/timezones/test
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/test 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/test 2011-06-30 20:44:13 UTC (rev 7697)
@@ -71,12 +71,23 @@
trial="$(type -p trial)";
if [ $# -gt 0 ]; then
- test_modules="$@";
+ test_modules="$@";
else
- test_modules="calendarserver twistedcaldav twext txdav ${m_twisted}";
+ test_modules="calendarserver twistedcaldav twext txdav contrib ${m_twisted}";
fi;
find "${wd}" -name \*.pyc -print0 | xargs -0 rm;
mkdir -p "${wd}/data";
cd "${wd}" && "${python}" "${trial}" --temp-directory="${wd}/data/trial" --rterrors ${random} ${until_fail} ${no_colour} ${coverage} ${test_modules};
+
+tmp="$(mktemp "/tmp/calendarserver_test.XXXXX")";
+
+echo "";
+echo "Running pyflakes...";
+cd "${wd}" && ./pyflakes "${test_modules}" | tee "${tmp}" 2>&1;
+if [ -s "${tmp}" ]; then
+ echo "**** Pyflakes says you have some code to clean up. ****";
+ exit 1;
+fi;
+rm -f "${tmp}";
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/enterprise
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/syntax.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/syntax.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/syntax.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -298,7 +298,18 @@
return CompoundComparison(self, 'in', subselect)
+ def StartsWith(self, other):
+ return CompoundComparison(self, "like", CompoundComparison(Constant(other), '||', Constant('%')))
+
+ def EndsWith(self, other):
+ return CompoundComparison(self, "like", CompoundComparison(Constant('%'), '||', Constant(other)))
+
+
+ def Contains(self, other):
+ return CompoundComparison(self, "like", CompoundComparison(Constant('%'), '||', CompoundComparison(Constant(other), '||', Constant('%'))))
+
+
class FunctionInvocation(ExpressionSyntax):
def __init__(self, function, *args):
self.function = function
@@ -377,6 +388,7 @@
+Count = Function("count")
Max = Function("max")
Len = Function("character_length", "length")
@@ -1093,6 +1105,9 @@
"""
def __init__(self, From, Where, Return=None):
+ """
+ If Where is None then all rows will be deleted.
+ """
self.From = From
self.Where = Where
self.Return = Return
@@ -1103,8 +1118,9 @@
allTables = self.From.tables()
result.text += 'delete from '
result.append(self.From.subSQL(metadata, allTables))
- result.text += ' where '
- result.append(self.Where.subSQL(metadata, allTables))
+ if self.Where is not None:
+ result.text += ' where '
+ result.append(self.Where.subSQL(metadata, allTables))
return self._returningClause(metadata, result, allTables)
Modified: CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/test/test_sqlsyntax.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/test/test_sqlsyntax.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twext/enterprise/dal/test/test_sqlsyntax.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -461,6 +461,63 @@
"select character_length(MYTEXT) from TEXTUAL"))
+ def test_startswith(self):
+ """
+ Test for the string starts with comparison.
+ (Note that this should be updated to use different techniques
+ as necessary in different databases.)
+ """
+ self.assertEquals(
+ Select([
+ self.schema.TEXTUAL.MYTEXT],
+ From=self.schema.TEXTUAL,
+ Where=self.schema.TEXTUAL.MYTEXT.StartsWith("test"),
+ ).toSQL(),
+ SQLFragment(
+ "select MYTEXT from TEXTUAL where MYTEXT like (? || ?)",
+ ["test", "%"]
+ )
+ )
+
+
+ def test_endswith(self):
+ """
+ Test for the string starts with comparison.
+ (Note that this should be updated to use different techniques
+ as necessary in different databases.)
+ """
+ self.assertEquals(
+ Select([
+ self.schema.TEXTUAL.MYTEXT],
+ From=self.schema.TEXTUAL,
+ Where=self.schema.TEXTUAL.MYTEXT.EndsWith("test"),
+ ).toSQL(),
+ SQLFragment(
+ "select MYTEXT from TEXTUAL where MYTEXT like (? || ?)",
+ ["%", "test"]
+ )
+ )
+
+
+ def test_contains(self):
+ """
+ Test for the string starts with comparison.
+ (Note that this should be updated to use different techniques
+ as necessary in different databases.)
+ """
+ self.assertEquals(
+ Select([
+ self.schema.TEXTUAL.MYTEXT],
+ From=self.schema.TEXTUAL,
+ Where=self.schema.TEXTUAL.MYTEXT.Contains("test"),
+ ).toSQL(),
+ SQLFragment(
+ "select MYTEXT from TEXTUAL where MYTEXT like (? || (? || ?))",
+ ["%", "test", "%"]
+ )
+ )
+
+
def test_insert(self):
"""
L{Insert.toSQL} generates an 'insert' statement with all the relevant
@@ -560,7 +617,7 @@
enough, as the code needs to actually retrieve the values from the out
parameters.
"""
- conn, pool, factory = self.simulateOracleConnection()
+ conn, _ignore_pool, factory = self.simulateOracleConnection()
i = Insert({self.schema.FOO.BAR: 40,
self.schema.FOO.BAZ: 50},
Return=(self.schema.FOO.BAR, self.schema.FOO.BAZ))
@@ -706,7 +763,13 @@
"delete from FOO where BAR = ?", [12])
)
+ self.assertEquals(
+ Delete(self.schema.FOO,
+ Where=None).toSQL(),
+ SQLFragment("delete from FOO")
+ )
+
def test_lock(self):
"""
L{Lock.exclusive} generates a ('lock table') statement, locking the
@@ -895,7 +958,7 @@
other statement types as well, specifically those with 'returning'
clauses.
"""
- conn, pool, factory = self.simulateOracleConnection()
+ conn, _ignore_pool, factory = self.simulateOracleConnection()
# Add 2 cursor variable values so that these will be used by
# FakeVariable.getvalue.
factory.varvals.extend([None, None])
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/internet
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/protocols
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/python
___________________________________________________________________
Added: svn:ignore
+ *.so
*.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/auth
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/channel
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/client
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/dav
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/dav/element
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/dav/method
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twext/web2/filter
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twisted/plugins
___________________________________________________________________
Modified: svn:ignore
- dropin.cache
+ dropin.cache
*.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/caldavxml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/caldavxml.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/caldavxml.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -57,6 +57,10 @@
"inbox-availability",
)
+caldav_query_extended_compliance = (
+ "calendar-query-extended",
+)
+
class CalDAVElement (davxml.WebDAVElement):
"""
CalDAV XML element.
@@ -387,16 +391,6 @@
# optimize them originals away
self.children = (data,)
- # Verify that we have valid calendar data, but don't call
- # validateForCalDAV() on the result, since some responses may
- # require a calendar-data element with iCalendar data not meant
- # for use as a CalDAV resource.
- #try:
- # self.calendar()
- #except ValueError, e:
- # log.err("Invalid iCalendar data (%s): %r" % (e, data))
- # raise
-
if "content-type" in attributes:
self.content_type = attributes["content-type"]
else:
@@ -637,7 +631,10 @@
(caldav_namespace, "comp-filter" ): (0, None),
(caldav_namespace, "prop-filter" ): (0, None),
}
- allowed_attributes = { "name": True }
+ allowed_attributes = {
+ "name": True,
+ "test": False,
+ }
class PropertyFilter (CalDAVElement):
"""
@@ -652,7 +649,10 @@
(caldav_namespace, "text-match" ): (0, 1),
(caldav_namespace, "param-filter" ): (0, None),
}
- allowed_attributes = { "name": True }
+ allowed_attributes = {
+ "name": True,
+ "test": False,
+ }
class ParameterFilter (CalDAVElement):
"""
@@ -698,7 +698,8 @@
allowed_attributes = {
"caseless": False,
- "negate-condition": False
+ "negate-condition": False,
+ "match-type": False,
}
class TimeZone (CalDAVTimeZoneElement):
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/client
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/datafilters
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/datafilters/test/test_peruserdata.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/datafilters/test/test_peruserdata.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/datafilters/test/test_peruserdata.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -18,11 +18,7 @@
from twistedcaldav.ical import Component
from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
-class PerUserDataFilterTestNotRecurring (twistedcaldav.test.util.TestCase):
-
- def test_public_noperuser(self):
-
- data = """BEGIN:VCALENDAR
+dataForTwoUsers = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
@@ -31,19 +27,38 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user01
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test01
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:OPAQUE
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+UID:12345-67890
+X-CALENDARSERVER-PERUSER-UID:user02
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:Test02
+TRIGGER;RELATED=START:-PT10M
+END:VALARM
+TRANSP:TRANSPARENT
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""".replace("\n", "\r\n")
-
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
- def test_public_oneuser(self):
-
- data = """BEGIN:VCALENDAR
+
+resultForUser1 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
@@ -52,23 +67,20 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user01
-BEGIN:X-CALENDARSERVER-PERINSTANCE
+TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
-DESCRIPTION:Test
+DESCRIPTION:Test01
TRIGGER;RELATED=START:-PT10M
END:VALARM
-TRANSP:OPAQUE
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
+END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")
- result01 = """BEGIN:VCALENDAR
+
+
+resultForUser2 = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
@@ -77,17 +89,20 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:OPAQUE
+TRANSP:TRANSPARENT
BEGIN:VALARM
ACTION:DISPLAY
-DESCRIPTION:Test
+DESCRIPTION:Test02
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")
- result02 = """BEGIN:VCALENDAR
+
+
+resultForOtherUser = """BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
@@ -96,19 +111,39 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")
+
+
+
+class PerUserDataFilterTestNotRecurring (twistedcaldav.test.util.TestCase):
+
+ def test_public_noperuser(self):
+ data = """BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+BEGIN:VEVENT
+UID:12345-67890
+DTSTART:20080601T120000Z
+DTEND:20080601T130000Z
+ATTENDEE:mailto:user1 at example.com
+ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
+ORGANIZER;CN=User 01:mailto:user1 at example.com
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n")
+
for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)), data)
for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), data)
- def test_public_twousers(self):
+ def test_public_oneuser(self):
data = """BEGIN:VCALENDAR
VERSION:2.0
@@ -119,6 +154,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -127,24 +163,12 @@
BEGIN:X-CALENDARSERVER-PERINSTANCE
BEGIN:VALARM
ACTION:DISPLAY
-DESCRIPTION:Test01
+DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
TRANSP:OPAQUE
END:X-CALENDARSERVER-PERINSTANCE
END:X-CALENDARSERVER-PERUSER
-BEGIN:X-CALENDARSERVER-PERUSER
-UID:12345-67890
-X-CALENDARSERVER-PERUSER-UID:user02
-BEGIN:X-CALENDARSERVER-PERINSTANCE
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test02
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
-TRANSP:TRANSPARENT
-END:X-CALENDARSERVER-PERINSTANCE
-END:X-CALENDARSERVER-PERUSER
END:VCALENDAR
""".replace("\n", "\r\n")
result01 = """BEGIN:VCALENDAR
@@ -156,11 +180,12 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
ACTION:DISPLAY
-DESCRIPTION:Test01
+DESCRIPTION:Test
TRIGGER;RELATED=START:-PT10M
END:VALARM
END:VEVENT
@@ -175,39 +200,41 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
-TRANSP:TRANSPARENT
-BEGIN:VALARM
-ACTION:DISPLAY
-DESCRIPTION:Test02
-TRIGGER;RELATED=START:-PT10M
-END:VALARM
END:VEVENT
END:VCALENDAR
""".replace("\n", "\r\n")
- result03 = """BEGIN:VCALENDAR
-VERSION:2.0
-PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
-BEGIN:VEVENT
-UID:12345-67890
-DTSTART:20080601T120000Z
-DTEND:20080601T130000Z
-ATTENDEE:mailto:user1 at example.com
-ATTENDEE:mailto:user2 at example.com
-ORGANIZER;CN=User 01:mailto:user1 at example.com
-END:VEVENT
-END:VCALENDAR
-""".replace("\n", "\r\n")
-
+
for item in (data, Component.fromString(data),):
self.assertEqual(str(PerUserDataFilter("user01").filter(item)), result01)
for item in (data, Component.fromString(data),):
self.assertEqual(str(PerUserDataFilter("user02").filter(item)), result02)
for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("user03").filter(item)), result03)
- for item in (data, Component.fromString(data),):
- self.assertEqual(str(PerUserDataFilter("").filter(item)), result03)
+ self.assertEqual(str(PerUserDataFilter("").filter(item)), result02)
+
+ def test_public_twousers(self):
+ """
+ A component with data for 2 users can return results for either of the
+ two users, or for a third user who has no per-user data embedded in it.
+ """
+
+ for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+ self.assertEqual(str(PerUserDataFilter("user01").filter(item)),
+ resultForUser1)
+ for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+ self.assertEqual(str(PerUserDataFilter("user02").filter(item)),
+ resultForUser2)
+ for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+ self.assertEqual(str(PerUserDataFilter("user03").filter(item)),
+ resultForOtherUser)
+ for item in (dataForTwoUsers, Component.fromString(dataForTwoUsers),):
+ self.assertEqual(str(PerUserDataFilter("").filter(item)),
+ resultForOtherUser)
+
+
+
class PerUserDataFilterTestRecurring (twistedcaldav.test.util.TestCase):
def test_public_noperuser(self):
@@ -221,6 +248,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -231,6 +259,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -252,6 +281,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -262,6 +292,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -287,6 +318,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -303,6 +335,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -322,6 +355,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -332,6 +366,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -355,6 +390,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -365,6 +401,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -399,6 +436,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -415,6 +453,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -434,6 +473,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -444,6 +484,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -467,6 +508,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -477,6 +519,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -503,6 +546,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -513,6 +557,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -532,6 +577,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -542,6 +588,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -565,6 +612,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -600,6 +648,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -616,6 +665,7 @@
DTEND:20080602T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -635,6 +685,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -659,6 +710,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -715,6 +767,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -731,6 +784,7 @@
DTEND:20080602T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -750,6 +804,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -766,6 +821,7 @@
DTEND:20080603T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -785,6 +841,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -810,6 +867,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -837,6 +895,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -857,6 +916,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -882,6 +942,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -895,6 +956,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -922,6 +984,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -941,6 +1004,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -966,6 +1030,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -987,6 +1052,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1018,6 +1084,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1028,6 +1095,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1041,6 +1109,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1051,6 +1120,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1078,6 +1148,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -1094,6 +1165,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -1113,6 +1185,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1123,6 +1196,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1148,6 +1222,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1158,6 +1233,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1179,6 +1255,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -1195,6 +1272,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -1214,6 +1292,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1224,6 +1303,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1258,6 +1338,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1268,6 +1349,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1289,6 +1371,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1299,6 +1382,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -1318,6 +1402,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1328,6 +1413,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1356,6 +1442,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1366,6 +1453,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1387,6 +1475,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -1403,6 +1492,7 @@
DTEND:20080602T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -1422,6 +1512,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1457,6 +1548,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1467,6 +1559,7 @@
DTEND:20080602T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1488,6 +1581,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -1504,6 +1598,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:TRANSPARENT
BEGIN:VALARM
@@ -1523,6 +1618,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1533,6 +1629,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1567,6 +1664,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1577,6 +1675,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1600,6 +1699,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1613,6 +1713,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1632,6 +1733,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1652,6 +1754,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -1671,6 +1774,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1696,6 +1800,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1728,6 +1833,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -1747,6 +1853,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1784,6 +1891,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1828,6 +1936,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1841,6 +1950,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1878,6 +1988,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -1918,6 +2029,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1932,6 +2044,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1952,6 +2065,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1972,6 +2086,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -1982,6 +2097,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1995,6 +2111,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2005,6 +2122,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2024,6 +2142,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -2045,6 +2164,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -2059,6 +2179,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2079,6 +2200,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -2099,6 +2221,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -2119,6 +2242,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2144,6 +2268,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2177,6 +2302,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -2193,6 +2319,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -2212,6 +2339,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2237,6 +2365,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2247,6 +2376,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2289,6 +2419,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -2309,6 +2440,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2335,6 +2467,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2369,6 +2502,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -2389,6 +2523,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2426,6 +2561,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2471,6 +2607,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -2487,6 +2624,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -2506,6 +2644,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2543,6 +2682,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2553,6 +2693,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2607,6 +2748,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -2626,6 +2768,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2664,6 +2807,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2697,6 +2841,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2711,6 +2856,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2748,6 +2894,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2787,6 +2934,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2797,6 +2945,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -2810,6 +2959,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2847,6 +2997,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2857,6 +3008,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2897,6 +3049,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -2910,6 +3063,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2948,6 +3102,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -2977,6 +3132,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -2991,6 +3147,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3011,6 +3168,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3032,6 +3190,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3046,6 +3205,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3066,6 +3226,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3076,6 +3237,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -3096,6 +3258,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -3109,6 +3272,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3129,6 +3293,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -3149,6 +3314,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -3168,6 +3334,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3194,6 +3361,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3226,6 +3394,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -3245,6 +3414,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3255,6 +3425,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3280,6 +3451,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3312,6 +3484,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -3332,6 +3505,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3358,6 +3532,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3390,6 +3565,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -3409,6 +3585,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3447,6 +3624,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3491,6 +3669,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -3510,6 +3689,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3520,6 +3700,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3575,6 +3756,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3619,6 +3801,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -3639,6 +3822,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3678,6 +3862,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3710,6 +3895,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3724,6 +3910,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3761,6 +3948,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3800,6 +3988,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3810,6 +3999,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -3823,6 +4013,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3860,6 +4051,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -3870,6 +4062,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3909,6 +4102,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -3923,6 +4117,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3962,6 +4157,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -3990,6 +4186,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4004,6 +4201,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4024,6 +4222,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4045,6 +4244,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -4065,6 +4265,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4091,6 +4292,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4124,6 +4326,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -4144,6 +4347,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4182,6 +4386,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4227,6 +4432,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4241,6 +4447,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4279,6 +4486,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4318,6 +4526,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4332,6 +4541,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4379,6 +4589,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4420,6 +4631,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4430,6 +4642,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -4443,6 +4656,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4453,6 +4667,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4472,6 +4687,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4482,6 +4698,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -4502,6 +4719,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -4518,6 +4736,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -4537,6 +4756,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4547,6 +4767,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4581,6 +4802,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4591,6 +4813,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4632,6 +4855,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
TRANSP:OPAQUE
@@ -4648,6 +4872,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -4667,6 +4892,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4677,6 +4903,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4732,6 +4959,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4742,6 +4970,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4795,6 +5024,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4805,6 +5035,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -4818,6 +5049,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4828,6 +5060,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4883,6 +5116,7 @@
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY
END:VEVENT
@@ -4893,6 +5127,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4934,6 +5169,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -4948,6 +5184,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -4969,6 +5206,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -4990,6 +5228,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -5010,6 +5249,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -5037,6 +5277,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -5071,6 +5312,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
TRANSP:OPAQUE
BEGIN:VALARM
@@ -5091,6 +5333,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -5131,6 +5374,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -5165,6 +5409,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -5179,6 +5424,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -5219,6 +5465,7 @@
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
END:VEVENT
BEGIN:X-CALENDARSERVER-PERUSER
@@ -5248,6 +5495,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=5
TRANSP:OPAQUE
@@ -5268,6 +5516,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
@@ -5324,6 +5573,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=5
END:VEVENT
@@ -5378,6 +5628,7 @@
DTEND:20080602T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
TRANSP:OPAQUE
@@ -5398,6 +5649,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
@@ -5445,6 +5697,7 @@
DTEND:20080602T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
@@ -5499,6 +5752,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
TRANSP:OPAQUE
@@ -5519,6 +5773,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RDATE:20080602T150000Z
RRULE:FREQ=DAILY;COUNT=10
@@ -5567,6 +5822,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
@@ -5613,6 +5869,7 @@
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
EXDATE:20080602T110000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
TRANSP:OPAQUE
@@ -5633,6 +5890,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
END:VEVENT
@@ -5680,6 +5938,7 @@
DTEND:20080601T120000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
EXDATE:20080602T110000Z
ORGANIZER;CN=User 01:mailto:user1 at example.com
RRULE:FREQ=DAILY;COUNT=10
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/dateops.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/dateops.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/dateops.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -29,7 +29,6 @@
"compareDateTime",
"differenceDateTime",
"timeRangesOverlap",
- "periodEnd",
"normalizePeriodList",
"clipPeriod"
]
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/appleopendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/appleopendirectory.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/appleopendirectory.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -33,7 +33,6 @@
from twext.web2.auth.digest import DigestedCredentials
from twistedcaldav.config import config
-from twistedcaldav.directory import augment
from twistedcaldav.directory.cachingdirectory import CachingDirectoryService,\
CachingDirectoryRecord
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
@@ -77,6 +76,7 @@
self.recordType_users,
self.recordType_groups,
),
+ 'augmentService' : None,
}
ignored = ('requireComputerRecord',)
params = self.getParams(params, defaults, ignored)
@@ -94,6 +94,7 @@
self.log_error("OpenDirectory (node=%s) Initialization error: %s" % (params['node'], e))
raise
+ self.augmentService = params['augmentService']
self.realmName = params['node']
self.directory = directory
self.node = params['node']
@@ -239,6 +240,7 @@
guids = set()
+ self.log_info("Looking up which groups %s is a member of" % (guid,))
try:
self.log_debug("opendirectory.queryRecordsWithAttribute_list(%r,%r,%r,%r,%r,%r,%r)" % (
self.directory,
@@ -299,6 +301,8 @@
if recordGUID:
guids.add(recordGUID)
+ self.log_info("%s is a member of %d groups" % (guid, len(guids)))
+
return guids
def proxiesForGUID(self, recordType, guid):
@@ -470,7 +474,7 @@
# TODO: this needs to be deferred but for now we hard code
# the deferred result because we know it is completing
# immediately.
- d = augment.AugmentService.getAugmentRecord(record.guid,
+ d = self.augmentService.getAugmentRecord(record.guid,
recordType)
d.addCallback(lambda x:record.addAugmentInformation(x))
@@ -757,7 +761,7 @@
# Look up augment information
# TODO: this needs to be deferred but for now we hard code the deferred result because
# we know it is completing immediately.
- d = augment.AugmentService.getAugmentRecord(record.guid,
+ d = self.augmentService.getAugmentRecord(record.guid,
recordType)
d.addCallback(lambda x:record.addAugmentInformation(x))
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/augment.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/augment.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -49,6 +49,7 @@
enabledForCalendaring=False,
autoSchedule=False,
enabledForAddressBooks=False,
+ enabledForLogin=True,
):
self.uid = uid
self.enabled = enabled
@@ -56,6 +57,7 @@
self.partitionID = partitionID
self.enabledForCalendaring = enabledForCalendaring
self.enabledForAddressBooks = enabledForAddressBooks
+ self.enabledForLogin = enabledForLogin
self.autoSchedule = autoSchedule
self.clonedFromDefault = False
@@ -114,6 +116,7 @@
enabled=True,
enabledForCalendaring=True,
enabledForAddressBooks=True,
+ enabledForLogin=True,
)
self.cachedRecords["Default"] = result
result = copy.deepcopy(result)
@@ -201,8 +204,6 @@
"""
raise NotImplementedError("Child class must define this.")
-
-AugmentService = AugmentDB() # Global augment service
class AugmentXMLDB(AugmentDB):
@@ -414,6 +415,7 @@
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_PARTITIONID, record.partitionID)
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_ENABLECALENDAR, "true" if record.enabledForCalendaring else "false")
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_ENABLEADDRESSBOOK, "true" if record.enabledForAddressBooks else "false")
+ addSubElement(recordNode, xmlaugmentsparser.ELEMENT_ENABLELOGIN, "true" if record.enabledForLogin else "false")
addSubElement(recordNode, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if record.autoSchedule else "false")
def refresh(self):
@@ -473,6 +475,7 @@
enabled=True,
enabledForCalendaring=True,
enabledForAddressBooks=True,
+ enabledForLogin=True,
)
# Compare previously seen modification time and size of each
@@ -525,11 +528,11 @@
"""
# Query for the record information
- results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE from AUGMENTS where UID = :1", (uid,)))
+ results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED from AUGMENTS where UID = :1", (uid,)))
if not results:
returnValue(None)
else:
- uid, enabled, serverid, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule = results[0]
+ uid, enabled, serverid, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule, enabledForLogin = results[0]
record = AugmentRecord(
uid = uid,
@@ -538,6 +541,7 @@
partitionID = partitionid,
enabledForCalendaring = enabledForCalendaring == "T",
enabledForAddressBooks = enabledForAddressBooks == "T",
+ enabledForLogin = enabledForLogin == "T",
autoSchedule = autoSchedule == "T",
)
@@ -600,6 +604,7 @@
("CALENDARING", "text(1)"),
("ADDRESSBOOKS", "text(1)"),
("AUTOSCHEDULE", "text(1)"),
+ ("LOGINENABLED", "text(1)"),
),
ifnotexists=True,
)
@@ -622,8 +627,8 @@
def _addRecord(self, record):
yield self.execute(
"""insert or replace into AUGMENTS
- (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE)
- values (:1, :2, :3, :4, :5, :6, :7)""",
+ (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED)
+ values (:1, :2, :3, :4, :5, :6, :7, :8)""",
(
record.uid,
"T" if record.enabled else "F",
@@ -632,6 +637,7 @@
"T" if record.enabledForCalendaring else "F",
"T" if record.enabledForAddressBooks else "F",
"T" if record.autoSchedule else "F",
+ "T" if record.enabledForLogin else "F",
)
)
@@ -652,8 +658,8 @@
def _addRecord(self, record):
yield self.execute(
"""insert into AUGMENTS
- (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE)
- values (:1, :2, :3, :4, :5, :6, :7)""",
+ (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED)
+ values (:1, :2, :3, :4, :5, :6, :7, :8)""",
(
record.uid,
"T" if record.enabled else "F",
@@ -662,6 +668,7 @@
"T" if record.enabledForCalendaring else "F",
"T" if record.enabledForAddressBooks else "F",
"T" if record.autoSchedule else "F",
+ "T" if record.enabledForLogin else "F",
)
)
@@ -669,8 +676,8 @@
def _modifyRecord(self, record):
yield self.execute(
"""update AUGMENTS set
- (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE) =
- (:1, :2, :3, :4, :5, :6, :7) where UID = :8""",
+ (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, LOGINENABLED) =
+ (:1, :2, :3, :4, :5, :6, :7 :8) where UID = :9""",
(
record.uid,
"T" if record.enabled else "F",
@@ -679,6 +686,7 @@
"T" if record.enabledForCalendaring else "F",
"T" if record.enabledForAddressBooks else "F",
"T" if record.autoSchedule else "F",
+ "T" if record.enabledForLogin else "F",
record.uid,
)
)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/calendaruserproxy.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/calendaruserproxy.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -596,12 +596,12 @@
# Cache miss; compute members and update cache
def gotMembersFromDB(dbmembers):
- members = set([row[0] for row in dbmembers])
+ members = set([row[0].encode("utf-8") for row in dbmembers])
d = self._memcacher.setMembers(principalUID, members)
d.addCallback(lambda _: members)
return d
- d = self.query("select MEMBER from GROUPS where GROUPNAME = :1", (principalUID,))
+ d = self.query("select MEMBER from GROUPS where GROUPNAME = :1", (principalUID.decode("utf-8"),))
d.addCallback(gotMembersFromDB)
return d
@@ -621,12 +621,12 @@
# Cache miss; compute memberships and update cache
def gotMembershipsFromDB(dbmemberships):
- memberships = set([row[0] for row in dbmemberships])
+ memberships = set([row[0].encode("utf-8") for row in dbmemberships])
d = self._memcacher.setMemberships(principalUID, memberships)
d.addCallback(lambda _: memberships)
return d
- d = self.query("select GROUPNAME from GROUPS where MEMBER = :1", (principalUID,))
+ d = self.query("select GROUPNAME from GROUPS where MEMBER = :1", (principalUID.decode("utf-8"),))
d.addCallback(gotMembershipsFromDB)
return d
@@ -647,7 +647,7 @@
"""
insert into GROUPS (GROUPNAME, MEMBER)
values (:1, :2)
- """, (principalUID, member,)
+ """, (principalUID.decode("utf-8"), member,)
)
def _add_to_db_one(self, principalUID, memberUID):
@@ -661,7 +661,7 @@
"""
insert into GROUPS (GROUPNAME, MEMBER)
values (:1, :2)
- """, (principalUID, memberUID,)
+ """, (principalUID.decode("utf-8"), memberUID.decode("utf-8"),)
)
def _delete_from_db(self, principalUID):
@@ -670,7 +670,7 @@
@param principalUID: the UID of the group principal to remove.
"""
- return self.execute("delete from GROUPS where GROUPNAME = :1", (principalUID,))
+ return self.execute("delete from GROUPS where GROUPNAME = :1", (principalUID.decode("utf-8"),))
def _delete_from_db_one(self, principalUID, memberUID):
"""
@@ -679,7 +679,7 @@
@param principalUID: the UID of the group principal to remove.
@param memberUID: the UID of the principal that is being removed as a member of this group.
"""
- return self.execute("delete from GROUPS where GROUPNAME = :1 and MEMBER = :2", (principalUID, memberUID,))
+ return self.execute("delete from GROUPS where GROUPNAME = :1 and MEMBER = :2", (principalUID.decode("utf-8"), memberUID.decode("utf-8"),))
def _delete_from_db_member(self, principalUID):
"""
@@ -687,7 +687,7 @@
@param principalUID: the UID of the member principal to remove.
"""
- return self.execute("delete from GROUPS where MEMBER = :1", (principalUID,))
+ return self.execute("delete from GROUPS where MEMBER = :1", (principalUID.decode("utf-8"),))
def _db_version(self):
"""
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/directory.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/directory.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -115,6 +115,11 @@
if credentials.authnPrincipal is None:
raise UnauthorizedLogin("No such user: %s" % (credentials.credentials.username,))
+ # See if record is enabledForLogin
+ if not credentials.authnPrincipal.record.isLoginEnabled():
+ raise UnauthorizedLogin("User not allowed to log in: %s" %
+ (credentials.credentials.username,))
+
# Handle Kerberos as a separate behavior
try:
from twistedcaldav.authkerb import NegotiateCredentials
@@ -358,6 +363,7 @@
calendarUserAddresses=set(), autoSchedule=False, enabledForCalendaring=None,
enabledForAddressBooks=None,
uid=None,
+ enabledForLogin=True,
**kwargs
):
assert service.realmName is not None
@@ -389,6 +395,7 @@
self.enabledForCalendaring = enabledForCalendaring
self.autoSchedule = autoSchedule
self.enabledForAddressBooks = enabledForAddressBooks
+ self.enabledForLogin = enabledForLogin
self.extras = kwargs
@@ -437,6 +444,7 @@
self.enabledForCalendaring = augment.enabledForCalendaring
self.enabledForAddressBooks = augment.enabledForAddressBooks
self.autoSchedule = augment.autoSchedule
+ self.enabledForLogin = augment.enabledForLogin
if (self.enabledForCalendaring or self.enabledForAddressBooks) and self.recordType == self.service.recordType_groups:
self.enabledForCalendaring = False
@@ -454,6 +462,7 @@
self.partitionID = ""
self.enabledForCalendaring = False
self.enabledForAddressBooks = False
+ self.enabledForLogin = False
def applySACLs(self):
@@ -472,6 +481,13 @@
% (username,))
self.enabledForAddressBooks = False
+ def isLoginEnabled(self):
+ """
+ Returns True if the user should be allowed to log in, based on the
+ enabledForLogin attribute, which is currently controlled by the
+ DirectoryService implementation.
+ """
+ return self.enabledForLogin
def members(self):
return ()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/ldapdirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/ldapdirectory.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/ldapdirectory.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -52,8 +52,8 @@
from twisted.cred.credentials import UsernamePassword
from twistedcaldav.directory.cachingdirectory import (CachingDirectoryService,
CachingDirectoryRecord)
-from twistedcaldav.directory import augment
from twistedcaldav.directory.directory import DirectoryConfigurationError
+from twistedcaldav.directory.augment import AugmentRecord
from twisted.internet.defer import succeed
class LdapDirectoryService(CachingDirectoryService):
@@ -74,6 +74,7 @@
"""
defaults = {
+ "augmentService" : None,
"cacheTimeout": 1,
"negativeCaching": False,
"restrictEnabledRecords": False,
@@ -98,6 +99,8 @@
"emailSuffix": None, # used only to synthesize email address
"filter": None, # additional filter for this type
"recordName": "uid", # uniquely identifies user records
+ "loginEnabledAttr" : "loginEnabled", # attribute controlling login
+ "loginEnabledValue" : "yes", # value of above attribute
},
"groups": {
"rdn": "ou=Group",
@@ -141,6 +144,7 @@
super(LdapDirectoryService, self).__init__(params["cacheTimeout"],
params["negativeCaching"])
+ self.augmentService = params["augmentService"]
self.realmName = params["uri"]
self.uri = params["uri"]
self.tls = params["tls"]
@@ -172,6 +176,8 @@
attrSet.add(self.groupSchema["nestedGroupsAttr"])
if self.groupSchema["memberIdAttr"]:
attrSet.add(self.groupSchema["memberIdAttr"])
+ if self.rdnSchema["users"]["loginEnabledAttr"]:
+ attrSet.add(self.rdnSchema["users"]["loginEnabledAttr"])
self.attrList = list(attrSet)
self.typeRDNs = {}
@@ -246,6 +252,7 @@
self.authLDAP = self.createLDAPConnection()
self.log_debug("Authenticating %s" % (dn,))
self.authLDAP.bind_s(dn, password)
+ self.log_debug("Authentication succeeded for %s" % (dn,))
@property
@@ -388,9 +395,10 @@
firstName = None
lastName = None
emailAddresses = set()
- calendarUserAddresses = set()
enabledForCalendaring = None
+ enabledForAddressBooks = None
uid = None
+ enabledForLogin = True
# First check for and add guid
guidAttr = self.rdnSchema["guidAttr"]
@@ -401,6 +409,7 @@
emailAddresses = self._getMultipleLdapAttributes(attrs, "mail")
emailSuffix = self.rdnSchema[recordType]["emailSuffix"]
+
if len(emailAddresses) == 0 and emailSuffix is not None:
emailPrefix = self._getUniqueLdapAttribute(attrs,
self.rdnSchema[recordType]["attr"])
@@ -413,16 +422,28 @@
"displayName", "gecos")
firstName = self._getUniqueLdapAttribute(attrs, "givenName")
lastName = self._getUniqueLdapAttribute(attrs, "sn", "surname")
- calendarUserAddresses = emailAddresses
enabledForCalendaring = True
+ enabledForAddressBooks = True
+
+ # Check login control attribute
+ loginEnabledAttr = self.rdnSchema[recordType]["loginEnabledAttr"]
+ if loginEnabledAttr:
+ loginEnabledValue = self.rdnSchema[recordType]["loginEnabledValue"]
+ enabledForLogin = self._getUniqueLdapAttribute(attrs,
+ loginEnabledAttr) == loginEnabledValue
+
elif recordType == self.recordType_groups:
fullName = self._getUniqueLdapAttribute(attrs, "cn")
enabledForCalendaring = False
+ enabledForAddressBooks = False
+ enabledForLogin = False
+
elif recordType in (self.recordType_resources,
self.recordType_locations):
fullName = self._getUniqueLdapAttribute(attrs, "cn")
- calendarUserAddresses = emailAddresses
enabledForCalendaring = True
+ enabledForAddressBooks = False
+ enabledForLogin = False
record = LdapDirectoryRecord(
service = self,
@@ -434,19 +455,23 @@
firstName = firstName,
lastName = lastName,
emailAddresses = emailAddresses,
- calendarUserAddresses = calendarUserAddresses,
- enabledForCalendaring = enabledForCalendaring,
uid = uid,
dn = dn,
attrs = attrs,
)
- # Look up augment information
- # TODO: this needs to be deferred but for now we hard code the
- # deferred result because we know it is completing immediately.
- d = augment.AugmentService.getAugmentRecord(record.guid,
- recordType)
- d.addCallback(lambda x:record.addAugmentInformation(x))
+ # Generate an augment record based on information retrieved from LDAP
+ augmentRecord = AugmentRecord(
+ guid,
+ enabled=True,
+ serverID="", # TODO: add to LDAP?
+ partitionID="", # TODO: add to LDAP?
+ enabledForCalendaring=enabledForCalendaring,
+ autoSchedule=False, # TODO: add to LDAP?
+ enabledForAddressBooks=enabledForAddressBooks, # TODO: add to LDAP?
+ enabledForLogin=enabledForLogin,
+ )
+ record.addAugmentInformation(augmentRecord)
return record
@@ -544,9 +569,9 @@
Carries out the work of a principal-property-search against LDAP
Returns a deferred list of directory records.
"""
-
records = []
+ self.log_debug("Peforming principal property search for %s" % (fields,))
recordTypes = [recordType] if recordType else self.recordTypes()
for recordType in recordTypes:
filter = buildFilter(self.attributeMapping, fields, operand=operand)
@@ -560,11 +585,13 @@
(ldap.dn.dn2str(base), filter))
results = self.ldap.search_s(ldap.dn.dn2str(base),
ldap.SCOPE_SUBTREE, filter, self.attrList)
+ self.log_debug("LDAP search returned %d results" % (len(results),))
for dn, attrs in results:
# Skip if group restriction is in place and guid is not
# a member
- if self.restrictedGUIDs is not None:
+ if (recordType != self.recordType_groups and
+ self.restrictedGUIDs is not None):
guidAttr = self.rdnSchema["guidAttr"]
if guidAttr:
guid = self._getUniqueLdapAttribute(attrs, guidAttr)
@@ -574,6 +601,7 @@
record = self._ldapResultToRecord(dn, attrs, recordType)
records.append(record)
+ self.log_debug("Principal property search matched %d records" % (len(records),))
return succeed(records)
@@ -618,8 +646,7 @@
self, service, recordType,
guid, shortNames, authIDs, fullName,
firstName, lastName, emailAddresses,
- calendarUserAddresses, enabledForCalendaring, uid,
- dn, attrs
+ uid, dn, attrs
):
super(LdapDirectoryRecord, self).__init__(
service = service,
@@ -631,8 +658,6 @@
firstName = firstName,
lastName = lastName,
emailAddresses = emailAddresses,
- calendarUserAddresses = calendarUserAddresses,
- enabledForCalendaring = enabledForCalendaring,
uid = uid,
)
@@ -824,3 +849,4 @@
raise DirectoryConfigurationError(msg)
return super(LdapDirectoryRecord, self).verifyCredentials(credentials)
+
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/principal.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/principal.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -52,7 +52,6 @@
from twistedcaldav.config import config
from twistedcaldav.cache import DisabledCacheNotifier, PropfindCacheMixin
from twistedcaldav.directory import calendaruserproxy
-from twistedcaldav.directory import augment
from twistedcaldav.directory.calendaruserproxy import CalendarUserProxyPrincipalResource
from twistedcaldav.directory.common import uidsResourceName
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord
@@ -62,7 +61,6 @@
from twistedcaldav.directory.idirectory import IDirectoryService
from twistedcaldav import caldavxml, customxml
from twistedcaldav.customxml import calendarserver_namespace
-from twistedcaldav.directory.wiki import getWikiACL
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
log = Logger()
@@ -71,18 +69,9 @@
def defaultAccessControlList(self):
return authReadACL
- @inlineCallbacks
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
- wikiACL = (yield getWikiACL(self, request))
- if wikiACL is not None:
- # ACL depends on wiki server...
- log.debug("Wiki ACL: %s" % (wikiACL.toxml(),))
- returnValue(wikiACL)
- else:
- # ...otherwise permissions are fixed, and are not subject to
- # inheritance rules, etc.
- returnValue(self.defaultAccessControlList())
+ return succeed(self.defaultAccessControlList())
@@ -790,9 +779,9 @@
@inlineCallbacks
def setAutoSchedule(self, autoSchedule):
self.record.autoSchedule = autoSchedule
- augmentRecord = (yield augment.AugmentService.getAugmentRecord(self.record.guid, self.record.recordType))
+ augmentRecord = (yield self.record.service.augmentService.getAugmentRecord(self.record.guid, self.record.recordType))
augmentRecord.autoSchedule = autoSchedule
- (yield augment.AugmentService.addAugmentRecords([augmentRecord]))
+ (yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
def getAutoSchedule(self):
return self.record.autoSchedule
@@ -879,6 +868,29 @@
return addresses
+ def canonicalCalendarUserAddress(self):
+ """
+ Return a CUA for this principal, preferring in this order:
+ urn:uuid: form
+ mailto: form
+ first in calendarUserAddresses( ) list
+ """
+
+ cua = ""
+ for candidate in self.calendarUserAddresses():
+ # Pick the first one, but urn:uuid: and mailto: can override
+ if not cua:
+ cua = candidate
+ # But always immediately choose the urn:uuid: form
+ if candidate.startswith("urn:uuid:"):
+ cua = candidate
+ break
+ # Prefer mailto: if no urn:uuid:
+ elif candidate.startswith("mailto:"):
+ cua = candidate
+ return cua
+
+
def enabledAsOrganizer(self):
if self.record.recordType == DirectoryService.recordType_users:
return True
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test-default.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test-default.xml 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test-default.xml 2011-06-30 20:44:13 UTC (rev 7697)
@@ -116,4 +116,15 @@
<enable-addressbook>true</enable-addressbook>
<auto-schedule>true</auto-schedule>
</record>
+ <record>
+ <uid>FC674703-8008-4A77-B80E-0DB55A9CE620</uid>
+ <enable-login>false</enable-login>
+ </record>
+ <record>
+ <uid>B473DC32-1B0D-45EE-9BAC-DA878AE9CE74</uid>
+ <enable-login>true</enable-login>
+ </record>
+ <record>
+ <uid>9F2B176D-B3F5-483A-AA63-0A1FC6E6D54B</uid>
+ </record>
</augments>
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test.xml
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test.xml 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/augments-test.xml 2011-06-30 20:44:13 UTC (rev 7697)
@@ -58,4 +58,15 @@
<enable-addressbook>true</enable-addressbook>
<auto-schedule>true</auto-schedule>
</record>
+ <record>
+ <uid>FC674703-8008-4A77-B80E-0DB55A9CE620</uid>
+ <enable-login>false</enable-login>
+ </record>
+ <record>
+ <uid>B473DC32-1B0D-45EE-9BAC-DA878AE9CE74</uid>
+ <enable-login>true</enable-login>
+ </record>
+ <record>
+ <uid>9F2B176D-B3F5-483A-AA63-0A1FC6E6D54B</uid>
+ </record>
</augments>
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_aggregate.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_aggregate.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_aggregate.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -62,8 +62,13 @@
"""
Returns an IDirectoryService.
"""
- augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
- xmlService = XMLDirectoryService({'xmlFile' : xmlFile})
+ xmlService = XMLDirectoryService(
+ {
+ 'xmlFile' : xmlFile,
+ 'augmentService' :
+ augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
+ }
+ )
xmlService.recordTypePrefix = xml_prefix
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_augment.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_augment.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_augment.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -35,6 +35,9 @@
{"uid":"5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1", "enabled":True, "partitionID":"00001", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
{"uid":"543D28BA-F74F-4D5F-9243-B3E3A61171E5", "enabled":True, "partitionID":"00002", "enabledForCalendaring":False, "enabledForAddressBooks":False, "autoSchedule":False},
{"uid":"6A73326A-F781-47E7-A9F8-AF47364D4152", "enabled":True, "partitionID":"00002", "enabledForCalendaring":True, "enabledForAddressBooks":True, "autoSchedule":True},
+ {"uid":"FC674703-8008-4A77-B80E-0DB55A9CE620", "enabledForLogin":False,}, # Explicitly false
+ {"uid":"B473DC32-1B0D-45EE-9BAC-DA878AE9CE74", "enabledForLogin":True,}, # Explicitly True
+ {"uid":"9F2B176D-B3F5-483A-AA63-0A1FC6E6D54B", "enabledForLogin":True,}, # Default is True
)
testRecordWildcardDefault = (
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_livedirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_livedirectory.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_livedirectory.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -67,9 +67,6 @@
from twistedcaldav.directory.test.test_xmlfile import augmentsFile
from twisted.internet.defer import inlineCallbacks
- augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
-
-
class LiveDirectoryTests(object):
def test_ldapRecordWithShortName(self):
@@ -153,6 +150,8 @@
def setUp(self):
params = {
+ "augmentService":
+ augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
"uri": "ldap://%s" % (testServer,),
"rdnSchema": {
"base": base,
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_opendirectory.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_opendirectory.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_opendirectory.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -55,9 +55,13 @@
def setUp(self):
super(OpenDirectory, self).setUp()
- augment.AugmentService = augment.AugmentXMLDB(xmlFiles=())
try:
- self._service = OpenDirectoryService({"node" : "/Search"})
+ self._service = OpenDirectoryService(
+ {
+ "node" : "/Search",
+ "augmentService": augment.AugmentXMLDB(xmlFiles=()),
+ }
+ )
except ImportError, e:
raise SkipTest("OpenDirectory module is not available: %s" % (e,))
@@ -451,10 +455,10 @@
def setUp(self):
super(OpenDirectorySubset, self).setUp()
- augment.AugmentService = augment.AugmentXMLDB(xmlFiles=())
self._service = OpenDirectoryService(
{
"node" : "/Search",
"recordTypes" : (DirectoryService.recordType_users, DirectoryService.recordType_groups),
+ "augmentService" : augment.AugmentXMLDB(xmlFiles=()),
}
)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_principal.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_principal.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -53,11 +53,12 @@
def setUp(self):
super(ProvisionedPrincipals, self).setUp()
- augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
self.directoryServices = (
XMLDirectoryService(
{
'xmlFile' : xmlFile,
+ 'augmentService' :
+ augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
}
),
)
@@ -430,6 +431,14 @@
record.enabledForCalendaring = False
self.failIf(recordResource.calendarUserAddresses())
+ def test_canonicalCalendarUserAddress(self):
+ """
+ DirectoryPrincipalResource.canonicalCalendarUserAddress()
+ """
+ for provisioningResource, recordType, recordResource, record in self._allRecords():
+ if record.enabledForCalendaring:
+ self.failUnless(recordResource.canonicalCalendarUserAddress().startswith("urn:uuid:"))
+
def test_addressBookHomeURLs(self):
"""
DirectoryPrincipalResource.addressBookHomeURLs(),
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipaldb.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipaldb.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipaldb.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -86,6 +86,21 @@
self.assertEqual(membershipsB, set(("A",)))
@inlineCallbacks
+ def test_normalDBNonAscii(self):
+
+ # Get the DB
+ db_path = os.path.abspath(self.mktemp())
+ db = ProxySqliteDB(db_path)
+ principalID = "Test \xe4\xbd\x90\xe8\x97\xa4"
+ yield db.setGroupMembers(principalID, ("B", "C", "D",))
+
+ membersA = yield db.getMembers(principalID)
+ membershipsB = yield db.getMemberships("B")
+
+ self.assertEqual(membersA, set(("B", "C", "D",)))
+ self.assertEqual(membershipsB, set((principalID,)))
+
+ @inlineCallbacks
def test_DBIndexed(self):
# Get the DB
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipalmembers.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -38,8 +38,13 @@
def setUp(self):
super(ProxyPrincipals, self).setUp()
- augment.AugmentService = augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,))
- self.directoryService = XMLDirectoryService({'xmlFile' : xmlFile})
+ self.directoryService = XMLDirectoryService(
+ {
+ 'xmlFile' : xmlFile,
+ 'augmentService' :
+ augment.AugmentXMLDB(xmlFiles=(augmentsFile.path,)),
+ }
+ )
calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB("proxies.sqlite")
# Set up a principals hierarchy for each service we're testing with
@@ -568,4 +573,4 @@
yield self._addProxy(principal, proxyType, proxyPrincipal)
memberships = yield proxyPrincipal._calendar_user_proxy_index().getMemberships(proxyPrincipal.principalUID())
for uid in memberships:
- subPrincipal = provisioningResource.principalForUID(uid)
+ provisioningResource.principalForUID(uid)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_xmlfile.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_xmlfile.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/test/test_xmlfile.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -97,9 +97,14 @@
Test XML file based directory implementation.
"""
def service(self):
- self.patch(augment, "AugmentService",
- augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)))
- directory = XMLDirectoryService({'xmlFile' : self.xmlFile()}, alwaysStat=True)
+ directory = XMLDirectoryService(
+ {
+ 'xmlFile' : self.xmlFile(),
+ 'augmentService' :
+ augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)),
+ },
+ alwaysStat=True
+ )
return directory
def test_changedXML(self):
@@ -162,7 +167,7 @@
</augments>
"""
)
- augment.AugmentService.refresh()
+ service.augmentService.refresh()
for recordType, expectedRecords in (
( DirectoryService.recordType_users , () ),
@@ -316,12 +321,17 @@
))
def test_recordTypesSubset(self):
- self.patch(augment, "AugmentService",
- augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)))
directory = XMLDirectoryService(
- {'xmlFile' : self.xmlFile(),
- 'recordTypes' : (DirectoryService.recordType_users,
- DirectoryService.recordType_groups)},
+ {
+ 'xmlFile' : self.xmlFile(),
+ 'augmentService' :
+ augment.AugmentXMLDB(xmlFiles=(self.augmentsFile().path,)),
+ 'recordTypes' :
+ (
+ DirectoryService.recordType_users,
+ DirectoryService.recordType_groups
+ ),
+ },
alwaysStat=True
)
self.assertEquals(set(("users", "groups")), set(directory.recordTypes()))
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/wiki.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/wiki.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/wiki.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -128,7 +128,38 @@
self.enabled = True
+ at inlineCallbacks
+def getWikiAccess(userID, wikiID):
+ """
+ Ask the wiki server we're paired with what level of access the userID has
+ for the given wikiID. Possible values are "read", "write", and "admin"
+ (which we treat as "write").
+ """
+ wikiConfig = config.Authentication.Wiki
+ proxy = Proxy(wikiConfig["URL"])
+ try:
+ log.debug("Looking up Wiki ACL for: user [%s], wiki [%s]" % (userID,
+ wikiID))
+ access = (yield proxy.callRemote(wikiConfig["WikiMethod"],
+ userID, wikiID))
+
+ log.debug("Wiki ACL result: user [%s], wiki [%s], access [%s]" % (userID,
+ wikiID, access))
+ returnValue(access)
+
+ except Fault, fault:
+
+ log.debug("Wiki ACL result: user [%s], wiki [%s], FAULT [%s]" % (userID,
+ wikiID, fault))
+
+ if fault.faultCode == 2: # non-existent user
+ raise HTTPError(StatusResponse(responsecode.FORBIDDEN, fault.faultString))
+
+ else: # fault.faultCode == 12, non-existent wiki
+ raise HTTPError(StatusResponse(responsecode.NOT_FOUND, fault.faultString))
+
+
@inlineCallbacks
def getWikiACL(resource, request):
"""
@@ -151,7 +182,6 @@
if hasattr(request, 'wikiACL'):
returnValue(request.wikiACL)
- wikiConfig = config.Authentication.Wiki
userID = "unauthenticated"
wikiID = resource.record.shortNames[0]
@@ -164,17 +194,9 @@
# TODO: better error handling
pass
- proxy = Proxy(wikiConfig["URL"])
try:
+ access = (yield getWikiAccess(userID, wikiID))
- log.debug("Looking up Wiki ACL for: user [%s], wiki [%s]" % (userID,
- wikiID))
- access = (yield proxy.callRemote(wikiConfig["WikiMethod"],
- userID, wikiID))
-
- log.debug("Wiki ACL result: user [%s], wiki [%s], access [%s]" % (userID,
- wikiID, access))
-
# The ACL we returns has ACEs for the end-user and the wiki principal
# in case authzUser is the wiki principal.
if access == "read":
@@ -246,18 +268,6 @@
)
)
-
- except Fault, fault:
-
- log.debug("Wiki ACL result: user [%s], wiki [%s], FAULT [%s]" % (userID,
- wikiID, fault))
-
- if fault.faultCode == 2: # non-existent user
- raise HTTPError(StatusResponse(responsecode.FORBIDDEN, fault.faultString))
-
- else: # fault.faultCode == 12, non-existent wiki
- raise HTTPError(StatusResponse(responsecode.NOT_FOUND, fault.faultString))
-
except HTTPError:
# pass through the HTTPError we might have raised above
raise
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlaugmentsparser.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlaugmentsparser.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlaugmentsparser.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -40,6 +40,7 @@
ELEMENT_HOSTEDAT = "hosted-at" # Backwards compatibility
ELEMENT_ENABLECALENDAR = "enable-calendar"
ELEMENT_ENABLEADDRESSBOOK = "enable-addressbook"
+ELEMENT_ENABLELOGIN = "enable-login"
ELEMENT_AUTOSCHEDULE = "auto-schedule"
ATTRIBUTE_REPEAT = "repeat"
@@ -55,6 +56,7 @@
ELEMENT_HOSTEDAT: "partitionID", # Backwards compatibility
ELEMENT_ENABLECALENDAR: "enabledForCalendaring",
ELEMENT_ENABLEADDRESSBOOK: "enabledForAddressBooks",
+ ELEMENT_ENABLELOGIN: "enabledForLogin",
ELEMENT_AUTOSCHEDULE: "autoSchedule",
}
@@ -104,6 +106,7 @@
ELEMENT_ENABLE,
ELEMENT_ENABLECALENDAR,
ELEMENT_ENABLEADDRESSBOOK,
+ ELEMENT_ENABLELOGIN,
ELEMENT_AUTOSCHEDULE,
):
fields[node.tag] = node.text == VALUE_TRUE
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlfile.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlfile.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directory/xmlfile.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -32,7 +32,6 @@
from twistedcaldav.config import config
from twistedcaldav.config import fullServerPath
-from twistedcaldav.directory import augment
from twistedcaldav.directory.directory import DirectoryService, DirectoryRecord, DirectoryError
from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser, XMLAccountRecord
from twistedcaldav.scheduling.cuaddress import normalizeCUAddr
@@ -70,6 +69,7 @@
),
'realmName' : '/Search',
'statSeconds' : 15,
+ 'augmentService' : None,
}
ignored = None
params = self.getParams(params, defaults, ignored)
@@ -77,6 +77,7 @@
self._recordTypes = params['recordTypes']
self.realmName = params['realmName']
self.statSeconds = params['statSeconds']
+ self.augmentService = params['augmentService']
super(XMLDirectoryService, self).__init__()
@@ -169,7 +170,7 @@
shortNames = tuple(xmlAccountRecord.shortNames),
xmlPrincipal = xmlAccountRecord,
)
- d = augment.AugmentService.getAugmentRecord(record.guid,
+ d = self.augmentService.getAugmentRecord(record.guid,
record.recordType)
d.addCallback(lambda x:record.addAugmentInformation(x))
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directorybackedaddressbook.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/directorybackedaddressbook.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -87,7 +87,12 @@
params = config.DirectoryAddressBook.params.copy()
params["directoryBackedAddressBook"] = self
- self.directory = directoryClass(params)
+ try:
+ self.directory = directoryClass(params)
+ except ImportError, e:
+ log.error("Unable to set up directory address book: %s" % (e,))
+ return succeed(None)
+
return self.directory.createCache()
#print ("DirectoryBackedAddressBookResource.provisionDirectory: provisioned")
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/ical.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/ical.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,3 +1,4 @@
+# -*- test-case-name: twistedcaldav.test.test_icalendar -*-
##
# Copyright (c) 2005-2011 Apple Inc. All rights reserved.
#
@@ -49,7 +50,7 @@
from pycalendar.componentbase import PyCalendarComponentBase
from pycalendar.datetime import PyCalendarDateTime
from pycalendar.duration import PyCalendarDuration
-from pycalendar.exceptions import PyCalendarInvalidData
+from pycalendar.exceptions import PyCalendarError
from pycalendar.period import PyCalendarPeriod
from pycalendar.property import PyCalendarProperty
from pycalendar.timezone import PyCalendarTimezone
@@ -136,6 +137,10 @@
ignoredComponents = ("VTIMEZONE", "X-CALENDARSERVER-PERUSER",)
+# Used for min/max time-range query limits
+minDateTime = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
+maxDateTime = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
+
class InvalidICalendarDataError(ValueError):
pass
@@ -340,7 +345,7 @@
cal = PyCalendar()
try:
result = cal.parse(stream)
- except PyCalendarInvalidData:
+ except PyCalendarError:
result = None
if not result:
stream.seek(0)
@@ -364,6 +369,22 @@
def parse(data): return clazz.fromString(data)
return allDataFromStream(IStream(stream), parse)
+
+ @classmethod
+ def newCalendar(cls):
+ """
+ Create and return an empty C{VCALENDAR} component.
+
+ @return: a new C{VCALENDAR} component with appropriate metadata
+ properties already set (version, product ID).
+ @rtype: an instance of this class
+ """
+ self = cls("VCALENDAR")
+ self.addProperty(Property("VERSION", "2.0"))
+ self.addProperty(Property("PRODID", iCalendarProductID))
+ return self
+
+
def __init__(self, name, **kwargs):
"""
Use this constructor to initialize an empty L{Component}.
@@ -402,6 +423,9 @@
self._parent = None
def __str__ (self):
+ """
+ NB This does not automatically include timezones in VCALENDAR objects.
+ """
return str(self._pycalendar)
def __repr__(self):
@@ -422,6 +446,8 @@
"""
Return text representation and include timezones if the option is on
"""
+ assert self.name() == "VCALENDAR", "Must be a VCALENDAR: %r" % (self,)
+
return self._pycalendar.getText(includeTimezones=includeTimezones)
# FIXME: Should this not be in __eq__?
@@ -649,6 +675,26 @@
return due.duplicateAsUTC() if due is not None else None
+ def getCompletedDateUTC(self):
+ """
+ Return the completed date or date-time for the specified component
+ converted to UTC.
+ @param component: the Component whose start should be returned.
+ @return: the datetime.date or datetime.datetime for the start.
+ """
+ completed = self.propertyValue("COMPLETED")
+ return completed.duplicateAsUTC() if completed is not None else None
+
+ def getCreatedDateUTC(self):
+ """
+ Return the created date or date-time for the specified component
+ converted to UTC.
+ @param component: the Component whose start should be returned.
+ @return: the datetime.date or datetime.datetime for the start.
+ """
+ created = self.propertyValue("CREATED")
+ return created.duplicateAsUTC() if created is not None else None
+
def getRecurrenceIDUTC(self):
"""
Return the recurrence-id for the specified component.
@@ -1227,9 +1273,11 @@
return changed
- def validCalendarForCalDAV(self):
+ def validCalendarData(self, doFix=True, doRaise=True):
"""
- @raise InvalidICalendarDataError: if the given calendar data is not valid.
+ @return: tuple of fixed, unfixed issues
+ @raise InvalidICalendarDataError: if the given calendar data is not valid and
+ cannot be fixed.
"""
if self.name() != "VCALENDAR":
log.debug("Not a calendar: %s" % (self,))
@@ -1238,34 +1286,30 @@
log.debug("Unknown resource type: %s" % (self,))
raise InvalidICalendarDataError("Unknown resource type")
- version = self.propertyValue("VERSION")
- if version != "2.0":
- msg = "Not a version 2.0 iCalendar (version=%s)" % (version,)
- log.debug(msg)
- raise InvalidICalendarDataError(msg)
+ # Do underlying iCalendar library validation with data fix
+ fixed, unfixed = self._pycalendar.validate(doFix=doFix)
+ if unfixed:
+ log.debug("Calendar data had unfixable problems:\n %s" % ("\n ".join(unfixed),))
+ if doRaise:
+ raise InvalidICalendarDataError("Calendar data had unfixable problems:\n %s" % ("\n ".join(unfixed),))
+ if fixed:
+ log.debug("Calendar data had fixable problems:\n %s" % ("\n ".join(fixed),))
+
+ return fixed, unfixed
- def validateForCalDAV(self):
+ def validCalendarForCalDAV(self, methodAllowed):
"""
+ @param methodAllowed: True if METHOD property is allowed, False otherwise.
@raise InvalidICalendarDataError: if the given calendar component is not valid for
use as a X{CalDAV} resource.
"""
- self.validCalendarForCalDAV()
# Disallowed in CalDAV-Access-08, section 4.1
- if self.hasProperty("METHOD"):
+ if not methodAllowed and self.hasProperty("METHOD"):
msg = "METHOD property is not allowed in CalDAV iCalendar data"
log.debug(msg)
raise InvalidICalendarDataError(msg)
- self.validateComponentsForCalDAV(False)
-
- def validateComponentsForCalDAV(self, method, fix=False):
- """
- @param method: True if METHOD property is allowed, False otherwise.
- @param fix: True to try and fix bogus data
- @raise InvalidICalendarDataError: if the given calendar component is not valid for
- use as a X{CalDAV} resource.
- """
#
# Must not contain more than one type of iCalendar component, except for
# the required timezone components, and component UIDs must match
@@ -1280,12 +1324,6 @@
#master_recurring = False
for subcomponent in self.subcomponents():
- # Disallowed in CalDAV-Access-08, section 4.1
- if not method and subcomponent.hasProperty("METHOD"):
- msg = "METHOD property is not allowed in CalDAV iCalendar data"
- log.debug(msg)
- raise InvalidICalendarDataError(msg)
-
if subcomponent.name() == "VTIMEZONE":
timezones.add(subcomponent.propertyValue("TZID"))
elif subcomponent.name() in ignoredComponents:
@@ -1351,32 +1389,6 @@
else:
component_rids.add(rid)
- # Check for mismatch in DTSTART and UNTIL value type
- # If they're not both date or both date-time, raise error
- if (subcomponent.hasProperty("DTSTART") and
- subcomponent.hasProperty("RRULE")):
- dtValue = subcomponent.propertyValue("DTSTART")
- dtutc = dtValue.duplicateAsUTC()
- # Using properties("RRULE") rather than getRRuleSet() here
- # because the dateutil rrule's _until values are datetime
- # even if the UNTIL is a date (and therefore we can't
- # check validity without doing the following):
- rrules = subcomponent._pycalendar.getRecurrenceSet()
- for rrule in rrules.getRules():
- if rrule.getUseUntil():
- if rrule.getUntil().isDateOnly() ^ dtValue.isDateOnly():
- msg = "Calendar resources must have matching type for DTSTART and UNTIL"
- log.debug(msg)
- if fix:
- log.debug("Fixing mismatch")
- rrule.getUntil().setDateOnly(dtValue.isDateOnly())
- if not dtValue.isDateOnly():
- rrule.getUntil().setHHMMSS(dtutc.getHours(), dtutc.getMinutes(), dtutc.getSeconds())
- rrule.getUntil().setTimezone(PyCalendarTimezone(utc=True))
- rrules.changed()
- else:
- raise InvalidICalendarDataError(msg)
-
timezone_refs.update(subcomponent.timezoneIDs())
#
@@ -1487,7 +1499,7 @@
return False
# First make sure components are all of the same time (excluding VTIMEZONE)
- self.validateComponentsForCalDAV(True)
+ self.validCalendarForCalDAV(methodAllowed=True)
# Next we could check the iTIP status for each type of method/component pair, however
# we can also leave that up to the server except for the REQUEST/VFREEBUSY case which
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/instance.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/instance.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/instance.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -165,16 +165,14 @@
if len(self.instances) > max_allowed_instances:
raise TooManyInstancesError()
- def _addMasterEventComponent(self, component, limit):
+ def _getMasterEventDetails(self, component):
"""
- Add the specified master VEVENT Component to the instance list, expanding it
- within the supplied time range.
- @param component: the Component to expand
- @param limit: the end L{PyCalendarDateTime} for expansion
+ Logic here comes from RFC4791 Section 9.9
"""
+
start = component.getStartDateUTC()
if start is None:
- return
+ return None
rulestart = component.propertyValue("DTSTART")
end = component.getEndDateUTC()
@@ -189,7 +187,22 @@
end = start + duration
else:
duration = differenceDateTime(start, end)
+
+ return (rulestart, start, end, duration,)
+ def _addMasterEventComponent(self, component, limit):
+ """
+ Add the specified master VEVENT Component to the instance list, expanding it
+ within the supplied time range.
+ @param component: the Component to expand
+ @param limit: the end L{PyCalendarDateTime} for expansion
+ """
+
+ details = self._getMasterEventDetails(component)
+ if details is None:
+ return
+ rulestart, start, end, duration = details
+
self._addMasterComponent(component, limit, rulestart, start, end, duration)
def _addOverrideEventComponent(self, component, limit, got_master):
@@ -202,23 +215,58 @@
#TODO: This does not take into account THISANDPRIOR - only THISANDFUTURE
- start = component.getStartDateUTC()
- if start is None:
+ details = self._getMasterEventDetails(component)
+ if details is None:
return
+ _ignore_rulestart, start, end, _ignore_duration = details
- end = component.getEndDateUTC()
- duration = None
- if end is None:
- if not start.isDateOnly():
- # Timed event with zero duration
- duration = PyCalendarDuration(days=0)
+ self._addOverrideComponent(component, limit, start, end, got_master)
+
+ def _getMasterToDoDetails(self, component):
+ """
+ Logic here comes from RFC4791 Section 9.9
+ """
+
+ dtstart = component.getStartDateUTC()
+ dtend = component.getEndDateUTC()
+ dtdue = component.getDueDateUTC()
+
+ # DTSTART and DURATION or DUE case
+ if dtstart is not None:
+ rulestart = component.propertyValue("DTSTART")
+ start = dtstart
+ if dtend is not None:
+ end = dtend
+ elif dtdue is not None:
+ end = dtdue
else:
- # All day event default duration is one day
- duration = PyCalendarDuration(days=1)
- end = start + duration
+ end = dtstart
+
+ # DUE case
+ elif dtdue is not None:
+ rulestart = component.propertyValue("DUE")
+ start = end = dtdue
+
+ # Fall back to COMPLETED or CREATED - cannot be recurring
+ else:
+ rulestart = None
+ from twistedcaldav.ical import maxDateTime, minDateTime
+ dtcreated = component.getCreatedDateUTC()
+ dtcompleted = component.getCompletedDateUTC()
+ if dtcompleted:
+ end = dtcompleted
+ start = dtcreated if dtcreated else dtend
+ elif dtcreated:
+ start = dtcreated
+ end = maxDateTime
+ else:
+ start = minDateTime
+ end = maxDateTime
- self._addOverrideComponent(component, limit, start, end, got_master)
+ duration = differenceDateTime(start, end)
+ return (rulestart, start, end, duration,)
+
def _addMasterToDoComponent(self, component, limit):
"""
Add the specified master VTODO Component to the instance list, expanding it
@@ -226,22 +274,13 @@
@param component: the Component to expand
@param limit: the end L{PyCalendarDateTime} for expansion
"""
- start = component.getStartDateUTC()
- due = component.getDueDateUTC()
-
- if start is None and due is None:
+ details = self._getMasterToDoDetails(component)
+ if details is None:
return
+ rulestart, start, end, duration = details
- rulestart = component.propertyValue("DTSTART")
- if start is None:
- start = due
- rulestart = component.propertyValue("DUE")
- elif due is None:
- due = start
- duration = differenceDateTime(start, due)
+ self._addMasterComponent(component, limit, rulestart, start, end, duration)
- self._addMasterComponent(component, limit, rulestart, start, due, duration)
-
def _addOverrideToDoComponent(self, component, limit, got_master):
"""
Add the specified overridden VTODO Component to the instance list, replacing
@@ -252,23 +291,17 @@
#TODO: This does not take into account THISANDPRIOR - only THISANDFUTURE
- start = component.getStartDateUTC()
- due = component.getDueDateUTC()
-
- if start is None and due is None:
+ details = self._getMasterToDoDetails(component)
+ if details is None:
return
+ _ignore_rulestart, start, end, _ignore_duration = details
- if start is None:
- start = due
- elif due is None:
- due = start
+ self._addOverrideComponent(component, limit, start, end, got_master)
- self._addOverrideComponent(component, limit, start, due, got_master)
-
def _addMasterComponent(self, component, limit, rulestart, start, end, duration):
rrules = component.getRecurrenceSet()
- if rrules is not None:
+ if rrules is not None and rulestart is not None:
# Do recurrence set expansion
expanded = []
limited = rrules.expand(rulestart, PyCalendarPeriod(start, limit), expanded)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/localization.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/localization.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/localization.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -17,7 +17,6 @@
from __future__ import with_statement
import gettext
import inspect
-import datetime
import os
import struct
import array
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/mail.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/mail.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/mail.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -529,6 +529,19 @@
# Service
#
+class MailGatewayService(service.MultiService):
+
+ def startService(self):
+ """
+ Purge old database tokens -- doing this in startService so that
+ it happens after we've shed privileges
+ """
+ service.MultiService.startService(self)
+ mailer = getattr(self, "mailer", None)
+ if mailer is not None:
+ mailer.purge()
+
+
class MailGatewayServiceMaker(LoggingMixIn):
implements(IPlugin, service.IServiceMaker)
@@ -543,7 +556,7 @@
config.Memcached.MaxClients,
)
- multiService = service.MultiService()
+ mailGatewayService = MailGatewayService()
settings = config.Scheduling['iMIP']
if settings['Enabled']:
@@ -560,18 +573,19 @@
# TODO: raise error?
self.log_error("Invalid iMIP type in configuration: %s" %
(mailType,))
- return multiService
+ return mailGatewayService
- client.setServiceParent(multiService)
+ client.setServiceParent(mailGatewayService)
-
# Set up /inbox -- server POSTs to it to send out iMIP invites
- IScheduleService(settings, mailer).setServiceParent(multiService)
+ IScheduleService(settings, mailer).setServiceParent(mailGatewayService)
else:
+ mailer = None
self.log_info("Mail Gateway Service not enabled")
- return multiService
+ mailGatewayService.mailer = mailer
+ return mailGatewayService
class IScheduleService(service.MultiService, LoggingMixIn):
@@ -609,9 +623,14 @@
if dataRoot is None:
dataRoot = config.DataRoot
self.db = MailGatewayTokensDatabase(dataRoot)
- days = config.Scheduling['iMIP']['InvitationDaysToLive']
+ self.days = config.Scheduling['iMIP']['InvitationDaysToLive']
+
+ def purge(self):
+ """
+ Purge old database tokens
+ """
self.db.purgeOldTokens(datetime.date.today() -
- datetime.timedelta(days=days))
+ datetime.timedelta(days=self.days))
def checkDSN(self, message):
# returns (isDSN, Action, icalendar attachment)
@@ -835,7 +854,7 @@
for attendeeProp in calendar.getAllAttendeeProperties():
cutype = attendeeProp.parameterValue('CUTYPE', None)
if cutype == "INDIVIDUAL":
- cn = attendeeProp.parameterValue("CN", None)
+ cn = attendeeProp.parameterValue("CN", None).decode("utf-8")
cuaddr = normalizeCUAddr(attendeeProp.value())
if cuaddr.startswith("mailto:"):
mailto = cuaddr[7:]
@@ -880,6 +899,7 @@
addressWithToken = "%s+%s@%s" % (pre, token, post)
organizerProperty = calendar.getOrganizerProperty()
+ organizerEmailAddress = organizerProperty.parameterValue("EMAIL", None)
organizerValue = organizerProperty.value()
organizerProperty.setValue("mailto:%s" % (addressWithToken,))
@@ -892,8 +912,8 @@
# The email's From will include the originator's real name email
# address if available. Otherwise it will be the server's email
# address (without # + addressing)
- if originator.startswith("mailto:"):
- orgEmail = fromAddr = originator[7:]
+ if organizerEmailAddress:
+ orgEmail = fromAddr = organizerEmailAddress
else:
fromAddr = serverAddress
orgEmail = None
@@ -909,11 +929,16 @@
else: # REPLY
inviteState = "reply"
- originator = originator.lower()
- if not originator.startswith("mailto:"):
- raise ValueError("Originator address '%s' must be mailto: for REPLY." % (originator,))
- formattedFrom = fromAddr = originator = originator[7:]
+ # Look up the attendee property corresponding to the originator
+ # of this reply
+ originatorAttendeeProperty = calendar.getAttendeeProperty([originator])
+ formattedFrom = fromAddr = originator = ""
+ if originatorAttendeeProperty:
+ originatorAttendeeEmailAddress = originatorAttendeeProperty.parameterValue("EMAIL", None)
+ if originatorAttendeeEmailAddress:
+ formattedFrom = fromAddr = originator = originatorAttendeeEmailAddress
+
organizerMailto = str(calendar.getOrganizer())
if not organizerMailto.lower().startswith("mailto:"):
raise ValueError("ORGANIZER address '%s' must be mailto: for REPLY." % (organizerMailto,))
@@ -1223,7 +1248,7 @@
results['month'] = dtStart.getMonth()
results['day'] = dtStart.getDay()
- summary = component.propertyValue("SUMMARY")
+ summary = component.propertyValue("SUMMARY").decode("utf-8")
if summary is None:
summary = ""
results['summary'] = summary
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/get.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/get.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/get.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -82,7 +82,7 @@
if self.accessMode:
# Non DAV:owner's have limited access to the data
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield self.isOwner(request))
# Now "filter" the resource calendar data
caldata = PrivateEventFilter(self.accessMode, isowner).filter(caldata)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_addressbook_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_addressbook_common.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_addressbook_common.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -198,12 +198,22 @@
"Could not parse vCard",
))
- # Valid vcard data for CalDAV check
+ # Valid vcard data check
result, message = self.validAddressDataCheck()
if not result:
log.err(message)
raise HTTPError(ErrorResponse(
responsecode.FORBIDDEN,
+ (carddav_namespace, "valid-address-data"),
+ description=message
+ ))
+
+ # Valid vcard data for CalDAV check
+ result, message = self.validCardDAVDataCheck()
+ if not result:
+ log.err(message)
+ raise HTTPError(ErrorResponse(
+ responsecode.FORBIDDEN,
(carddav_namespace, "valid-addressbook-object-resource"),
"Invalid vCard data",
))
@@ -283,8 +293,8 @@
def validAddressDataCheck(self):
"""
- Check that the vcard data is valid vCard.
- @return: tuple: (True/False if the vcard data is valid,
+ Check that the calendar data is valid iCalendar.
+ @return: tuple: (True/False if the calendar data is valid,
log message string).
"""
result = True
@@ -294,13 +304,29 @@
message = "Empty resource not allowed in vcard collection"
else:
try:
- self.vcard.validForCardDAV()
+ self.vcard.validVCardData()
except ValueError, e:
result = False
message = "Invalid vcard data: %s" % (e,)
return result, message
+ def validCardDAVDataCheck(self):
+ """
+ Check that the vcard data is valid vCard.
+ @return: tuple: (True/False if the vcard data is valid,
+ log message string).
+ """
+ result = True
+ message = ""
+ try:
+ self.vcard.validForCardDAV()
+ except ValueError, e:
+ result = False
+ message = "vCard data does not conform to CardDAV requirements: %s" % (e,)
+
+ return result, message
+
def validSizeCheck(self):
"""
Make sure that the content-type of the source resource is text/vcard.
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_common.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/put_common.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -468,7 +468,7 @@
message = "Empty resource not allowed in calendar collection"
else:
try:
- self.calendar.validCalendarForCalDAV()
+ self.calendar.validCalendarData()
except ValueError, e:
result = False
message = "Invalid calendar data: %s" % (e,)
@@ -484,10 +484,7 @@
result = True
message = ""
try:
- if self.isiTIP:
- self.calendar.validateComponentsForCalDAV(True)
- else:
- self.calendar.validateForCalDAV()
+ self.calendar.validCalendarForCalDAV(methodAllowed=self.isiTIP)
except ValueError, e:
result = False
message = "Calendar data does not conform to CalDAV requirements: %s" % (e,)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_calendar_query.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_calendar_query.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_calendar_query.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -174,7 +174,7 @@
filteredaces = (yield calresource.inheritedACEsforChildren(request))
# Check private events access status
- isowner = (yield calresource.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield calresource.isOwner(request))
# Check for disabled access
if filteredaces is not None:
@@ -229,7 +229,7 @@
timezone = tuple(tz.calendar().subcomponents())[0]
# Check private events access status
- isowner = (yield calresource.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield calresource.isOwner(request))
calendar = (yield calresource.iCalendarForUser(request))
yield queryCalendarObjectResource(calresource, uri, None, calendar, timezone)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_multiget_common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_multiget_common.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/method/report_multiget_common.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -139,7 +139,7 @@
disabled = True
# Check private events access status
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield self.isOwner(request))
elif self.isAddressBookCollection():
requestURIis = "addressbook"
@@ -352,7 +352,7 @@
filteredaces = (yield parent.inheritedACEsforChildren(request))
# Check private events access status
- isowner = (yield parent.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield parent.isOwner(request))
else:
name = unquote(resource_uri[resource_uri.rfind("/") + 1:])
if (resource_uri != request.uri) or not self.exists():
@@ -376,7 +376,7 @@
filteredaces = (yield parent.inheritedACEsforChildren(request))
# Check private events access status
- isowner = (yield parent.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield parent.isOwner(request))
# Check privileges - must have at least DAV:read
try:
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/notify.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/notify.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/notify.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -689,7 +689,7 @@
iq = IQ(self.xmlStream)
pubsubElement = iq.addElement('pubsub', defaultUri=self.pubsubNS)
publishElement = pubsubElement.addElement('publish')
- publishElement['node'] = nodeName
+ publishElement['node'] = nodeName.decode("utf-8")
if self.settings["NodeConfiguration"]["pubsub#deliver_payloads"] == '1':
itemElement = publishElement.addElement('item')
itemElement.addElement('plistfrag', defaultUri='plist-apple')
@@ -742,7 +742,7 @@
iq = IQ(self.xmlStream)
pubsubElement = iq.addElement('pubsub', defaultUri=self.pubsubNS)
child = pubsubElement.addElement('create')
- child['node'] = nodeName
+ child['node'] = nodeName.decode("utf-8")
d = iq.send(to=self.settings['ServiceAddress'])
d.addCallback(self.createNodeSuccess, nodeName, publish)
d.addErrback(self.createNodeFailure, nodeName, publish)
@@ -794,7 +794,7 @@
child = iq.addElement('pubsub',
defaultUri=self.pubsubNS+"#owner")
child = child.addElement('configure')
- child['node'] = nodeName
+ child['node'] = nodeName.decode("utf-8")
d = iq.send(to=self.settings['ServiceAddress'])
d.addCallback(self.requestConfigurationFormSuccess, nodeName,
publish)
@@ -829,7 +829,7 @@
filledPubSub = filledIq.addElement('pubsub',
defaultUri=self.pubsubNS+"#owner")
filledConfigure = filledPubSub.addElement('configure')
- filledConfigure['node'] = nodeName
+ filledConfigure['node'] = nodeName.decode("utf-8")
filledForm = filledConfigure.addElement('x',
defaultUri='jabber:x:data')
filledForm['type'] = 'submit'
@@ -859,7 +859,7 @@
cancelPubSub = cancelIq.addElement('pubsub',
defaultUri=self.pubsubNS+"#owner")
cancelConfig = cancelPubSub.addElement('configure')
- cancelConfig['node'] = nodeName
+ cancelConfig['node'] = nodeName.decode("utf-8")
cancelX = cancelConfig.addElement('x',
defaultUri='jabber:x:data')
cancelX['type'] = 'cancel'
@@ -940,7 +940,7 @@
pubsubElement = iq.addElement('pubsub',
defaultUri=self.pubsubNS+"#owner")
publishElement = pubsubElement.addElement('delete')
- publishElement['node'] = nodeName
+ publishElement['node'] = nodeName.decode("utf-8")
self.sendDebug("Deleting (%s)" % (nodeName,), iq)
d = iq.send(to=self.settings['ServiceAddress'])
d.addCallback(self.deleteNodeSuccess, nodeName)
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookquery.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookquery.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -26,8 +26,7 @@
"sqladdressbookquery",
]
-from twistedcaldav.query import expression, sqlgenerator
-from twistedcaldav import carddavxml
+from twistedcaldav.query import expression, sqlgenerator, addressbookqueryfilter
# SQL Index column (field) names
@@ -76,33 +75,32 @@
# Test for <<field>> != "*"
return expression.isExpression(fields["UID"], "", True)
- # Handle text-match
- tm = None
- if propfilter.qualifier and isinstance(propfilter.qualifier, carddavxml.TextMatch):
- if propfilter.qualifier.negate:
- tm = expression.notcontainsExpression(fields[propfilter.filter_name], str(propfilter.qualifier), propfilter.qualifier)
- else:
- tm = expression.containsExpression(fields[propfilter.filter_name], str(propfilter.qualifier), propfilter.qualifier)
-
- # Handle embedded parameters - we do not right now as our Index does not handle them
+ # Handle embedded parameters/text-match
params = []
- if len(propfilter.filters) > 0:
- raise ValueError
+ for filter in propfilter.filters:
+ if isinstance(filter, addressbookqueryfilter.TextMatch):
+ if filter.match_type == "equals":
+ tm = expression.isnotExpression if filter.negate else expression.isExpression
+ elif filter.match_type == "contains":
+ tm = expression.notcontainsExpression if filter.negate else expression.containsExpression
+ elif filter.match_type == "starts-with":
+ tm = expression.notstartswithExpression if filter.negate else expression.startswithExpression
+ elif filter.match_type == "ends-with":
+ tm = expression.notendswithExpression if filter.negate else expression.endswithExpression
+ params.append(tm(fields[propfilter.filter_name], str(filter.text), True))
+ else:
+ # No embedded parameters - not right now as our Index does not handle them
+ raise ValueError
+
+ # Now build return expression
if len(params) > 1:
- paramsExpression = expression.orExpression[params]
+ if propfilter.propfilter_test == "anyof":
+ return expression.orExpression(params)
+ else:
+ return expression.andExpression(params)
elif len(params) == 1:
- paramsExpression = params[0]
+ return params[0]
else:
- paramsExpression = None
-
- # Now build return expression
- if (tm is not None) and (paramsExpression is not None):
- return expression.andExpression([tm, paramsExpression])
- elif tm is not None:
- return tm
- elif paramsExpression is not None:
- return paramsExpression
- else:
return None
def sqladdressbookquery(filter, addressbookid=None, generator=sqlgenerator.sqlgenerator):
@@ -116,7 +114,7 @@
"""
try:
expression = addressbookquery(filter, generator.FIELDS)
- sql = generator(expression, addressbookid)
+ sql = generator(expression, addressbookid, None)
return sql.generate()
except ValueError:
return None
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookqueryfilter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookqueryfilter.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/addressbookqueryfilter.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2011 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.
@@ -69,7 +69,7 @@
if len(self.children) > 0:
allof = self.filter_test == "allof"
for propfilter in self.children:
- if allof != propfilter.test(vcard):
+ if allof != propfilter._match(vcard):
return not allof
return allof
else:
@@ -153,7 +153,7 @@
if len(self.filters) > 0:
allof = self.propfilter_test == "allof"
for filter in self.filters:
- if allof != filter.test(item):
+ if allof != filter._match(item):
return not allof
return allof
else:
@@ -164,7 +164,7 @@
Limits a search to specific properties.
"""
- def test(self, vcard):
+ def _match(self, vcard):
# At least one property must match (or is-not-defined is set)
for property in vcard.properties():
if property.name().upper() == self.filter_name.upper() and self.match(property): break
@@ -188,7 +188,7 @@
Limits a search to specific parameters.
"""
- def test(self, property):
+ def _match(self, property):
# At least one parameter must match (or is-not-defined is set)
result = not self.defined
@@ -246,7 +246,7 @@
else:
self.match_type = "contains"
- def test(self, item):
+ def _match(self, item):
"""
Match the text for the item.
If the item is a property, then match the property value,
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarquery.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarquery.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -48,15 +48,18 @@
assert vcalfilter.filter_name == "VCALENDAR"
if len(vcalfilter.filters) > 0:
+ # Determine logical expression grouping
+ logical = expression.andExpression if vcalfilter.filter_test == "allof" else expression.orExpression
+
# Only comp-filters are handled
for _ignore in [x for x in vcalfilter.filters if not isinstance(x, calendarqueryfilter.ComponentFilter)]:
raise ValueError
- return compfilterListExpression(vcalfilter.filters, fields)
+ return compfilterListExpression(vcalfilter.filters, fields, logical)
else:
return expression.allExpression()
-def compfilterListExpression(compfilters, fields):
+def compfilterListExpression(compfilters, fields, logical):
"""
Create an expression for a list of comp-filter elements.
@@ -67,7 +70,7 @@
if len(compfilters) == 1:
return compfilterExpression(compfilters[0], fields)
else:
- return expression.orExpression([compfilterExpression(c, fields) for c in compfilters])
+ return logical([compfilterExpression(c, fields) for c in compfilters])
def compfilterExpression(compfilter, fields):
"""
@@ -81,7 +84,10 @@
if not compfilter.defined:
# Test for TYPE != <<component-type name>>
return expression.isnotExpression(fields["TYPE"], compfilter.filter_name, True)
-
+
+ # Determine logical expression grouping
+ logical = expression.andExpression if compfilter.filter_test == "allof" else expression.orExpression
+
expressions = []
if isinstance(compfilter.filter_name, str):
expressions.append(expression.isExpression(fields["TYPE"], compfilter.filter_name, True))
@@ -98,7 +104,7 @@
for p in [x for x in compfilter.filters if isinstance(x, calendarqueryfilter.PropertyFilter)]:
props.append(propfilterExpression(p, fields))
if len(props) > 1:
- propsExpression = expression.orExpression[props]
+ propsExpression = logical(props)
elif len(props) == 1:
propsExpression = props[0]
else:
@@ -109,7 +115,7 @@
for _ignore in [x for x in compfilter.filters if isinstance(x, calendarqueryfilter.ComponentFilter)]:
raise ValueError
if len(comps) > 1:
- compsExpression = expression.orExpression[comps]
+ compsExpression = logical(comps)
elif len(comps) == 1:
compsExpression = comps[0]
else:
@@ -117,7 +123,7 @@
# Now build compound expression
if ((propsExpression is not None) and (compsExpression is not None)):
- expressions.append(expression.orExpression([propsExpression, compsExpression]))
+ expressions.append(logical([propsExpression, compsExpression]))
elif propsExpression is not None:
expressions.append(propsExpression)
elif compsExpression is not None:
@@ -143,6 +149,9 @@
# Test for <<field>> != "*"
return expression.isExpression(fields["UID"], "", True)
+ # Determine logical expression grouping
+ logical = expression.andExpression if propfilter.filter_test == "allof" else expression.orExpression
+
# Handle time-range - we cannot do this with our Index right now
if propfilter.qualifier and isinstance(propfilter.qualifier, calendarqueryfilter.TimeRange):
raise ValueError
@@ -150,17 +159,22 @@
# Handle text-match
tm = None
if propfilter.qualifier and isinstance(propfilter.qualifier, calendarqueryfilter.TextMatch):
- if propfilter.qualifier.negate:
- tm = expression.notcontainsExpression(fields[propfilter.filter_name], propfilter.qualifier.text, propfilter.qualifier.caseless)
- else:
- tm = expression.containsExpression(fields[propfilter.filter_name], propfilter.qualifier.text, propfilter.qualifier.caseless)
+ if propfilter.qualifier.match_type == "equals":
+ tm = expression.isnotExpression if propfilter.qualifier.negate else expression.isExpression
+ elif propfilter.qualifier.match_type == "contains":
+ tm = expression.notcontainsExpression if propfilter.qualifier.negate else expression.containsExpression
+ elif propfilter.qualifier.match_type == "starts-with":
+ tm = expression.notstartswithExpression if propfilter.qualifier.negate else expression.startswithExpression
+ elif propfilter.qualifier.match_type == "ends-with":
+ tm = expression.notendswithExpression if propfilter.qualifier.negate else expression.endswithExpression
+ tm = tm(fields[propfilter.filter_name], propfilter.qualifier.text, propfilter.qualifier.caseless)
# Handle embedded parameters - we do not right now as our Index does not handle them
params = []
for _ignore in propfilter.filters:
raise ValueError
if len(params) > 1:
- paramsExpression = expression.orExpression[params]
+ paramsExpression = logical(params)
elif len(params) == 1:
paramsExpression = params[0]
else:
@@ -168,7 +182,7 @@
# Now build return expression
if (tm is not None) and (paramsExpression is not None):
- return expression.andExpression([tm, paramsExpression])
+ return logical([tm, paramsExpression])
elif tm is not None:
return tm
elif paramsExpression is not None:
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarqueryfilter.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarqueryfilter.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/calendarqueryfilter.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2009-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2009-2011 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.
@@ -184,6 +184,11 @@
self.filter_name = self.filter_name.encode("utf-8")
self.defined = not self.qualifier or not isinstance(qualifier, IsNotDefined)
+ filter_test = xml_element.attributes.get("test", "allof")
+ if filter_test not in ("anyof", "allof"):
+ raise ValueError("Test must be only one of anyof, allof")
+ self.filter_test = filter_test
+
def match(self, item, access=None):
"""
Returns True if the given calendar item (either a component, property or parameter value)
@@ -197,10 +202,11 @@
if self.qualifier and not self.qualifier.match(item, access): return False
if len(self.filters) > 0:
+ allof = self.filter_test == "allof"
for filter in self.filters:
- if filter._match(item, access):
- return True
- return False
+ if allof != filter._match(item, access):
+ return not allof
+ return allof
else:
return True
@@ -224,10 +230,11 @@
if self.qualifier and not self.qualifier.matchinstance(item, self.instances): return False
if len(self.filters) > 0:
+ allof = self.filter_test == "allof"
for filter in self.filters:
- if filter._match(item, access):
- return True
- return False
+ if allof != filter._match(item, access):
+ return not allof
+ return allof
else:
return True
@@ -489,6 +496,18 @@
else:
self.negate = False
+ if "match-type" in xml_element.attributes:
+ self.match_type = xml_element.attributes["match-type"]
+ if self.match_type not in (
+ "equals",
+ "contains",
+ "starts-with",
+ "ends-with",
+ ):
+ self.match_type = "contains"
+ else:
+ self.match_type = "contains"
+
def match(self, item, access):
"""
Match the text for the item.
@@ -508,25 +527,29 @@
def _textCompare(s):
if self.caseless:
- if s.lower().find(test) != -1:
- return True, not self.negate
+ s = s.lower()
+
+ if self.match_type == "equals":
+ return s == test
+ elif self.match_type == "contains":
+ return s.find(test) != -1
+ elif self.match_type == "starts-with":
+ return s.startswith(test)
+ elif self.match_type == "ends-with":
+ return s.endswith(test)
else:
- if s.find(test) != -1:
- return True, not self.negate
- return False, False
+ return False
for value in values:
# NB Its possible that we have a text list value which appears as a Python list,
# so we need to check for that and iterate over the list.
if isinstance(value, list):
for subvalue in value:
- matched, result = _textCompare(unicode(subvalue, "utf-8"))
- if matched:
- return result
+ if _textCompare(unicode(subvalue, "utf-8")):
+ return not self.negate
else:
- matched, result = _textCompare(unicode(value, "utf-8"))
- if matched:
- return result
+ if _textCompare(unicode(value, "utf-8")):
+ return not self.negate
return self.negate
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/expression.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/expression.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/expression.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -32,6 +32,10 @@
"notcontainsExpression",
"isExpression",
"isnotExpression",
+ "startswithExpression",
+ "notstartswithExpression",
+ "endswithExpression",
+ "notendswithExpression",
"inExpression",
"notinExpression",
]
@@ -55,7 +59,22 @@
return False
-class allExpression(object):
+ def _collapsedExpression(self):
+ return self
+
+ def andWith(self, other):
+ if isinstance(other, andExpression):
+ return andExpression((self._collapsedExpression(),) + tuple(other.expressions))
+ else:
+ return andExpression((self._collapsedExpression(), other._collapsedExpression(),))
+
+ def orWith(self, other):
+ if isinstance(other, orExpression):
+ return orExpression((self._collapsedExpression(),) + tuple(other.expressions))
+ else:
+ return orExpression((self._collapsedExpression(), other._collapsedExpression(),))
+
+class allExpression(baseExpression):
"""
Match everything.
"""
@@ -99,6 +118,12 @@
return True
+ def _collapsedExpression(self):
+ if self.multi() and len(self.expressions) == 1:
+ return self.expressions[0]._collapsedExpression()
+ else:
+ return self
+
class notExpression(logicExpression):
"""
Logical NOT operation.
@@ -135,6 +160,10 @@
def operator(self):
return "AND"
+ def andWith(self, other):
+ self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+ return self
+
class orExpression(logicExpression):
"""
Logical OR operation.
@@ -146,6 +175,10 @@
def operator(self):
return "OR"
+ def orWith(self, other):
+ self.expressions = tuple(self.expressions) + (other._collapsedExpression(),)
+ return self
+
class timerangeExpression(baseExpression):
"""
CalDAV time-range comparison expression.
@@ -193,7 +226,7 @@
super(notcontainsExpression, self).__init__(field, text, caseless)
def operator(self):
- return " does not contain"
+ return "does not contain"
class isExpression(textcompareExpression):
"""
@@ -217,6 +250,50 @@
def operator(self):
return "is not"
+class startswithExpression(textcompareExpression):
+ """
+ Text STARTSWITH (sub-string match) expression.
+ """
+
+ def __init__(self, field, text, caseless):
+ super(startswithExpression, self).__init__(field, text, caseless)
+
+ def operator(self):
+ return "starts with"
+
+class notstartswithExpression(textcompareExpression):
+ """
+ Text NOT STARTSWITH (sub-string match) expression.
+ """
+
+ def __init__(self, field, text, caseless):
+ super(notstartswithExpression, self).__init__(field, text, caseless)
+
+ def operator(self):
+ return "does not start with"
+
+class endswithExpression(textcompareExpression):
+ """
+ Text STARTSWITH (sub-string match) expression.
+ """
+
+ def __init__(self, field, text, caseless):
+ super(endswithExpression, self).__init__(field, text, caseless)
+
+ def operator(self):
+ return "ends with"
+
+class notendswithExpression(textcompareExpression):
+ """
+ Text NOT STARTSWITH (sub-string match) expression.
+ """
+
+ def __init__(self, field, text, caseless):
+ super(notendswithExpression, self).__init__(field, text, caseless)
+
+ def operator(self):
+ return "does not end with"
+
class inExpression(textcompareExpression):
"""
Text IN (exact string match to one of the supplied items) expression.
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/sqlgenerator.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/sqlgenerator.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/sqlgenerator.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2006-2009 Apple Inc. All rights reserved.
+# Copyright (c) 2006-2011 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.
@@ -30,21 +30,25 @@
class sqlgenerator(object):
- FROM =" from "
- WHERE =" where "
- RESOURCEDB = "RESOURCE"
- TIMESPANDB = "TIMESPAN"
- TRANSPARENCYDB = "TRANSPARENCY"
- PERUSERDB = "PERUSER"
- NOTOP = "NOT "
- ANDOP = " AND "
- OROP = " OR "
- CONTAINSOP = " GLOB "
- NOTCONTAINSOP = " NOT GLOB "
- ISOP = " == "
- ISNOTOP = " != "
- INOP = " IN "
- NOTINOP = " NOT IN "
+ FROM =" from "
+ WHERE =" where "
+ RESOURCEDB = "RESOURCE"
+ TIMESPANDB = "TIMESPAN"
+ TRANSPARENCYDB = "TRANSPARENCY"
+ PERUSERDB = "PERUSER"
+ NOTOP = "NOT "
+ ANDOP = " AND "
+ OROP = " OR "
+ CONTAINSOP = " GLOB "
+ NOTCONTAINSOP = " NOT GLOB "
+ ISOP = " == "
+ ISNOTOP = " != "
+ STARTSWITHOP = " GLOB "
+ NOTSTARTSWITHOP = " NOT GLOB "
+ ENDSWITHOP = " GLOB "
+ NOTENDSWITHOP = " NOT GLOB "
+ INOP = " IN "
+ NOTINOP = " NOT IN "
FIELDS = {
"TYPE": "RESOURCE.TYPE",
@@ -187,6 +191,30 @@
self.sout.write(self.ISNOTOP)
self.addArgument(expr.text)
+ # STARTSWITH
+ elif isinstance(expr, expression.startswithExpression):
+ self.sout.write(expr.field)
+ self.sout.write(self.STARTSWITHOP)
+ self.addArgument(self.startswithArgument(expr.text))
+
+ # NOT STARTSWITH
+ elif isinstance(expr, expression.notstartswithExpression):
+ self.sout.write(expr.field)
+ self.sout.write(self.NOTSTARTSWITHOP)
+ self.addArgument(self.startswithArgument(expr.text))
+
+ # ENDSWITH
+ elif isinstance(expr, expression.endswithExpression):
+ self.sout.write(expr.field)
+ self.sout.write(self.ENDSWITHOP)
+ self.addArgument(self.endswithArgument(expr.text))
+
+ # NOT ENDSWITH
+ elif isinstance(expr, expression.notendswithExpression):
+ self.sout.write(expr.field)
+ self.sout.write(self.NOTENDSWITHOP)
+ self.addArgument(self.endswithArgument(expr.text))
+
# IN
elif isinstance(expr, expression.inExpression):
self.sout.write(expr.field)
@@ -259,6 +287,12 @@
def containsArgument(self, arg):
return "*%s*" % (arg,)
+ def startswithArgument(self, arg):
+ return "%s*" % (arg,)
+
+ def endswithArgument(self, arg):
+ return "*%s" % (arg,)
+
if __name__ == "__main__":
e1 = expression.isExpression("TYPE", "VEVENT", False)
Copied: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/test/test_expression.py (from rev 7695, CalendarServer/trunk/twistedcaldav/query/test/test_expression.py)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/test/test_expression.py (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/query/test/test_expression.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,166 @@
+##
+# Copyright (c) 2011 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 twistedcaldav.query import expression
+import twistedcaldav.test.util
+
+class Tests(twistedcaldav.test.util.TestCase):
+
+ def test_andWith(self):
+
+ tests = (
+ (
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) AND (is(B, 2, True) OR is(C, 3, True)))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "(is(A, 1, True) AND is(B, 2, True) AND is(C, 3, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) AND is(B, 2, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "((is(A, 1, True) OR is(B, 2, True)) AND is(C, 3, True))"
+ ),
+ )
+
+ for expr1, expr2, result in tests:
+ self.assertEqual(str(expr1.andWith(expr2)), result, msg="Failed on %s" % (result,))
+
+ def test_orWith(self):
+
+ tests = (
+ (
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.andExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) OR (is(B, 2, True) AND is(C, 3, True)))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ )),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.isExpression("A", "1", True),
+ expression.orExpression((
+ expression.isExpression("B", "2", True),
+ expression.isExpression("C", "3", True),
+ )),
+ "(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.andExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "((is(A, 1, True) AND is(B, 2, True)) OR is(C, 3, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ )),
+ expression.isExpression("B", "2", True),
+ "(is(A, 1, True) OR is(B, 2, True))"
+ ),
+ (
+ expression.orExpression((
+ expression.isExpression("A", "1", True),
+ expression.isExpression("B", "2", True),
+ )),
+ expression.isExpression("C", "3", True),
+ "(is(A, 1, True) OR is(B, 2, True) OR is(C, 3, True))"
+ ),
+ )
+
+ for expr1, expr2, result in tests:
+ self.assertEqual(str(expr1.orWith(expr2)), result, msg="Failed on %s" % (result,))
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/resource.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/resource.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -51,8 +51,7 @@
from twext.web2.dav.resource import AccessDeniedError, DAVPrincipalCollectionResource,\
davPrivilegeSet
from twext.web2.dav.resource import TwistedACLInheritable
-from twext.web2.dav.util import joinURL, parentForURL, normalizeURL,\
- unimplemented
+from twext.web2.dav.util import joinURL, parentForURL, normalizeURL
from twext.web2.http import HTTPError, RedirectResponse, StatusResponse, Response
from twext.web2.http_headers import MimeType
from twext.web2.stream import MemoryStream
@@ -878,7 +877,7 @@
"""
Return the DAV:owner property value (MUST be a DAV:href or None).
"""
-
+
isVirt = self.isVirtualShare()
if isVirt:
parent = (yield self.locateParent(request, self._share.hosturl))
@@ -928,33 +927,21 @@
returnValue(None)
- def isOwner(self, request, adminprincipals=False, readprincipals=False):
+ @inlineCallbacks
+ def isOwner(self, request):
"""
- Determine whether the DAV:owner of this resource matches the currently authorized principal
- in the request. Optionally test for admin or read principals and allow those.
+ Determine whether the DAV:owner of this resource matches the currently
+ authorized principal in the request, or if the user is a read-only or
+ read-write administrator.
"""
+ current = self.currentPrincipal(request)
+ if current in config.AllAdminPrincipalObjects:
+ returnValue(True)
+ if davxml.Principal((yield self.owner(request))) == current:
+ returnValue(True)
+ returnValue(False)
- def _gotOwner(owner):
- current = self.currentPrincipal(request)
- if davxml.Principal(owner) == current:
- return True
-
- if adminprincipals:
- for principal in config.AdminPrincipals:
- if davxml.Principal(davxml.HRef(principal)) == current:
- return True
- if readprincipals:
- for principal in config.AdminPrincipals:
- if davxml.Principal(davxml.HRef(principal)) == current:
- return True
-
- return False
-
- d = self.owner(request)
- d.addCallback(_gotOwner)
- return d
-
##
# DAVResource
##
@@ -1535,15 +1522,15 @@
@inlineCallbacks
- def iCalendarTextFiltered(self, isowner, accessUID=None):
+ def iCalendarFiltered(self, isowner, accessUID=None):
# Now "filter" the resource calendar data
caldata = PrivateEventFilter(self.accessMode, isowner).filter(
- (yield self.iCalendarText())
+ (yield self.iCalendar())
)
if accessUID:
caldata = PerUserDataFilter(accessUID).filter(caldata)
- returnValue(str(caldata))
+ returnValue(caldata)
def iCalendarText(self):
@@ -2094,7 +2081,7 @@
@return: a C{int} containing the maximum allowed bytes if this
collection is quota-controlled, or C{None} if not quota controlled.
"""
- return config.UserQuota if config.UserQuota != 0 else None
+ return self._newStoreHome.quotaAllowedBytes()
def currentQuotaUse(self, request):
"""
@@ -2602,7 +2589,7 @@
if defaultAddressBookProperty and len(defaultAddressBookProperty.children) == 1:
defaultAddressBook = str(defaultAddressBookProperty.children[0])
adbk = (yield request.locateResource(str(defaultAddressBook)))
- if adbk is not None and adbk.exists() and isAddressBookCollectionResource(adbk):
+ if adbk is not None and isAddressBookCollectionResource(adbk) and adbk.exists() and not adbk.isVirtualShare():
returnValue(defaultAddressBookProperty)
# Default is not valid - we have to try to pick one
@@ -2624,7 +2611,7 @@
if len(new_adbk) == 1:
adbkURI = str(new_adbk[0])
adbk = (yield request.locateResource(str(new_adbk[0])))
- if adbk is None or not adbk.exists() or not isAddressBookCollectionResource(adbk):
+ if adbk is None or not adbk.exists() or not isAddressBookCollectionResource(adbk) or adbk.isVirtualShare():
# Validate that href's point to a valid addressbook.
raise HTTPError(ErrorResponse(
responsecode.CONFLICT,
@@ -2685,7 +2672,7 @@
defaultAddressBookURL = joinURL(self.url(), "addressbook")
defaultAddressBook = (yield self.makeRegularChild("addressbook"))
if defaultAddressBook is None or not defaultAddressBook.exists():
- getter = iter((yield self._newStoreHome.addressbooks()))
+ getter = iter((yield self._newStoreHome.addressbooks())) # These are only unshared children
# FIXME: the back-end should re-provision a default addressbook here.
# Really, the dead property shouldn't be necessary, and this should
# be entirely computed by a back-end method like 'defaultAddressBook()'
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/schedule.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/schedule.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -168,7 +168,7 @@
if defaultCalendarProperty and len(defaultCalendarProperty.children) == 1:
defaultCalendar = str(defaultCalendarProperty.children[0])
cal = (yield request.locateResource(str(defaultCalendar)))
- if cal is not None and isCalendarCollectionResource(cal) and cal.exists():
+ if cal is not None and isCalendarCollectionResource(cal) and cal.exists() and not cal.isVirtualShare():
returnValue(defaultCalendarProperty)
# Default is not valid - we have to try to pick one
@@ -223,7 +223,7 @@
calURI = str(new_calendar[0])
cal = (yield request.locateResource(str(new_calendar[0])))
# TODO: check that owner of the new calendar is the same as owner of this inbox
- if cal is None or not cal.exists() or not isCalendarCollectionResource(cal):
+ if cal is None or not cal.exists() or not isCalendarCollectionResource(cal) or cal.isVirtualShare():
# Validate that href's point to a valid calendar.
raise HTTPError(ErrorResponse(
responsecode.CONFLICT,
@@ -263,16 +263,17 @@
defaultCalendarURL = joinURL(calendarHomeURL, "calendar")
defaultCalendar = (yield request.locateResource(defaultCalendarURL))
if defaultCalendar is None or not defaultCalendar.exists():
- getter = iter((yield self.parent._newStoreHome.calendars()))
# FIXME: the back-end should re-provision a default calendar here.
# Really, the dead property shouldn't be necessary, and this should
# be entirely computed by a back-end method like 'defaultCalendar()'
- try:
- aCalendar = getter.next()
- except StopIteration:
- raise RuntimeError("No calendars at all.")
+ for calendarName in (yield self.parent._newStoreHome.listCalendars()): # These are only unshared children
+ if calendarName != "inbox":
+ aCalendar = calendarName
+ break
+ else:
+ raise RuntimeError("No valid calendars to use as a default calendar.")
- defaultCalendarURL = joinURL(calendarHomeURL, aCalendar.name())
+ defaultCalendarURL = joinURL(calendarHomeURL, aCalendar)
self.writeDeadProperty(
caldavxml.ScheduleDefaultCalendarURL(
Property changes on: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/icaldiff.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/icaldiff.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/icaldiff.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -283,7 +283,7 @@
# Get all EXDATEs in UTC
exdates = set()
for exdate in master.properties("EXDATE"):
- exdates.update([value.getValue().adjustToUTC() for value in exdate.value()])
+ exdates.update([value.getValue().duplicate().adjustToUTC() for value in exdate.value()])
return exdates, map, master
@@ -347,7 +347,9 @@
overridden = self.newCalendar.overriddenComponent(rid)
self.newCalendar.removeComponent(overridden)
if self.newMaster:
- self.newMaster.addProperty(Property("EXDATE", [rid,]))
+ # Use the original R-ID value so we preserve the timezone
+ original_rid = component.propertyValue("RECURRENCE-ID")
+ self.newMaster.addProperty(Property("EXDATE", [original_rid,]))
# Derive a new component in the new calendar for each new one in setnew
for key in setnew - setold:
@@ -578,7 +580,7 @@
newdue = component.getProperty("DUE")
if newdue is not None:
- newdue.value().adjustToUTC()
+ newdue = newdue.value().duplicate().adjustToUTC()
# Recurrence rules - we need to normalize the order of the value parts
newrrules = set()
@@ -595,14 +597,14 @@
for rdate in rdates:
for value in rdate.value():
if isinstance(PyCalendarDateTime()):
- value.adjustToUTC()
+ value = value.duplicate().adjustToUTC()
newrdates.add(value)
# EXDATEs
newexdates = set()
exdates = component.properties("EXDATE")
for exdate in exdates:
- newexdates.update([value.getValue().adjustToUTC() for value in exdate.value()])
+ newexdates.update([value.getValue().duplicate().adjustToUTC() for value in exdate.value()])
return timeRange.getStart(), timeRange.getEnd(), newdue, newrrules, newrdates, newexdates
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/implicit.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/implicit.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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.
@@ -336,15 +336,10 @@
(caldav_namespace, "invalid-originator"),
"Originator not enabled",
))
-
- # Pick the first mailto cu address or the first other type
- for item in self.originatorPrincipal.calendarUserAddresses():
- if not self.originator:
- self.originator = item
- if item.startswith("mailto:"):
- self.originator = item
- break
+ # Pick the canonical CUA:
+ self.originator = self.originatorPrincipal.canonicalCalendarUserAddress()
+
# Get the ORGANIZER and verify it is the same for all components
try:
self.organizer = self.calendar.validOrganizerForScheduling()
@@ -553,9 +548,45 @@
# Check to see whether a change to R-ID's happened
if rid == "":
- if "RRULE" in props or "DTSTART" in props and self.calendar.masterComponent().hasProperty("RRULE"):
+
+ if "DTSTART" in props and self.calendar.masterComponent().hasProperty("RRULE"):
+ # DTSTART change with RRULE present is always a reschedule
recurrence_reschedule = True
+ elif "RRULE" in props:
+
+ # Need to see if the RRULE change is a simple truncation or expansion - i.e. a change to
+ # COUNT or UNTIL only. If so we don't need to treat this as a complete re-schedule.
+
+ # Start off assuming they are different
+ recurrence_reschedule = True
+
+ # Get each RRULE (can be only one in the master)
+ oldrrule = tuple(self.oldcalendar.masterComponent().properties("RRULE"))
+ oldrrule = oldrrule[0].value() if len(oldrrule) else None
+ newrrule = tuple(self.calendar.masterComponent().properties("RRULE"))
+ newrrule = newrrule[0].value() if len(newrrule) else None
+
+ if newrrule is not None and oldrrule is not None:
+
+ # Normalize the rrules by removing COUNT/UNTIL and then compare
+ oldrrule = oldrrule.duplicate()
+ newrrule = newrrule.duplicate()
+
+ oldrrule.setUseUntil(False)
+ oldrrule.setUntil(None)
+ oldrrule.setUseCount(False)
+ oldrrule.setCount(0)
+
+ newrrule.setUseUntil(False)
+ newrrule.setUntil(None)
+ newrrule.setUseCount(False)
+ newrrule.setCount(0)
+
+ # If they are equal we have a simple change - no overall reschedule
+ if newrrule == oldrrule:
+ recurrence_reschedule = False
+
if checkOrganizerValue:
oldOrganizer = self.oldcalendar.getOrganizer()
newOrganizer = self.calendar.getOrganizer()
@@ -915,7 +946,7 @@
local_organizer = type(self.organizerAddress) in (LocalCalendarUser, PartitionedCalendarUser, OtherServerCalendarUser,)
if config.Scheduling.iMIP.Enabled and self.organizerAddress.cuaddr.lower().startswith("mailto:"):
- return True
+ return is_server
if not local_organizer and is_server:
# Coerce ORGANIZER to SCHEDULE-AGENT=NONE
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/scheduler.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/scheduling/scheduler.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,4 +1,4 @@
-##
+
# Copyright (c) 2005-2010 Apple Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -173,13 +173,8 @@
if originatorPrincipalURL:
originatorPrincipal = (yield self.request.locateResource(originatorPrincipalURL))
if originatorPrincipal:
- # Pick the first mailto cu address or the first other type
- for item in originatorPrincipal.calendarUserAddresses():
- if not originator:
- originator = item
- if item.startswith("mailto:"):
- originator = item
- break
+ # Pick the canonical CUA:
+ originator = originatorPrincipal.canonicalCalendarUserAddress()
if not originator:
log.err("%s request must have Originator" % (self.method,))
@@ -305,7 +300,7 @@
def checkCalendarData(self):
# Must be a valid calendar
try:
- self.calendar.validCalendarForCalDAV()
+ self.calendar.validCalendarData()
except ValueError, e:
log.err("%s request calendar component is not valid:%s %s" % (self.method, e, self.calendar,))
raise HTTPError(ErrorResponse(
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/sharing.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/sharing.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -38,6 +38,7 @@
from twistedcaldav import customxml, caldavxml
from twistedcaldav.config import config
from twistedcaldav.customxml import calendarserver_namespace
+from twistedcaldav.directory.wiki import WikiDirectoryService, getWikiAccess
from twistedcaldav.linkresource import LinkFollowerMixIn
from twistedcaldav.sql import AbstractSQLDatabase, db_prefix
@@ -267,11 +268,28 @@
assert self._isVirtualShare, "Only call this for a virtual share"
+ wikiAccessMethod = kwargs.get("wikiAccessMethod", getWikiAccess)
+
# Direct shares use underlying privileges of shared collection
if self._share.sharetype == SHARETYPE_DIRECT:
original = (yield request.locateResource(self._share.hosturl))
- result = (yield original.accessControlList(request, *args, **kwargs))
- returnValue(result)
+ owner = yield original.ownerPrincipal(request)
+ if owner.record.recordType == WikiDirectoryService.recordType_wikis:
+ # Access level comes from what the wiki has granted to the
+ # sharee
+ userID = self._shareePrincipal.record.guid
+ wikiID = owner.record.shortNames[0]
+ inviteAccess = (yield wikiAccessMethod(userID, wikiID))
+ if inviteAccess == "read":
+ inviteAccess = "read-only"
+ elif inviteAccess in ("write", "admin"):
+ inviteAccess = "read-write"
+ else:
+ inviteAccess = None
+ else:
+ result = (yield original.accessControlList(request, *args,
+ **kwargs))
+ returnValue(result)
else:
# Invite shares use access mode from the invite
@@ -281,68 +299,69 @@
)
if invite is None:
returnValue(davxml.ACL())
+ inviteAccess = invite.access
- userprivs = [
- ]
- if invite.access in ("read-only", "read-write", "read-write-schedule",):
- userprivs.append(davxml.Privilege(davxml.Read()))
- userprivs.append(davxml.Privilege(davxml.ReadACL()))
- userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
- if invite.access in ("read-only",):
- userprivs.append(davxml.Privilege(davxml.WriteProperties()))
- if invite.access in ("read-write", "read-write-schedule",):
- userprivs.append(davxml.Privilege(davxml.Write()))
- proxyprivs = list(userprivs)
- proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
+ userprivs = [
+ ]
+ if inviteAccess in ("read-only", "read-write", "read-write-schedule",):
+ userprivs.append(davxml.Privilege(davxml.Read()))
+ userprivs.append(davxml.Privilege(davxml.ReadACL()))
+ userprivs.append(davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()))
+ if inviteAccess in ("read-only",):
+ userprivs.append(davxml.Privilege(davxml.WriteProperties()))
+ if inviteAccess in ("read-write", "read-write-schedule",):
+ userprivs.append(davxml.Privilege(davxml.Write()))
+ proxyprivs = list(userprivs)
+ proxyprivs.remove(davxml.Privilege(davxml.ReadACL()))
- aces = (
- # Inheritable specific access for the resource's associated principal.
+ aces = (
+ # Inheritable specific access for the resource's associated principal.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(self._shareePrincipal.principalURL())),
+ davxml.Grant(*userprivs),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
+
+ if self.isCalendarCollection():
+ aces += (
+ # Inheritable CALDAV:read-free-busy access for authenticated users.
davxml.ACE(
- davxml.Principal(davxml.HRef(self._shareePrincipal.principalURL())),
- davxml.Grant(*userprivs),
- davxml.Protected(),
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
TwistedACLInheritable(),
),
)
- if self.isCalendarCollection():
- aces += (
- # Inheritable CALDAV:read-free-busy access for authenticated users.
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(davxml.Privilege(caldavxml.ReadFreeBusy())),
- TwistedACLInheritable(),
- ),
- )
+ # Give read access to config.ReadPrincipals
+ aces += config.ReadACEs
- # Give read access to config.ReadPrincipals
- aces += config.ReadACEs
+ # Give all access to config.AdminPrincipals
+ aces += config.AdminACEs
- # Give all access to config.AdminPrincipals
- aces += config.AdminACEs
-
- if config.EnableProxyPrincipals:
- aces += (
- # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
- davxml.ACE(
- davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-read/"))),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
- ),
- davxml.Protected(),
- TwistedACLInheritable(),
+ if config.EnableProxyPrincipals:
+ aces += (
+ # DAV:read/DAV:read-current-user-privilege-set access for this principal's calendar-proxy-read users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-read/"))),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet()),
),
- # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
- davxml.ACE(
- davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-write/"))),
- davxml.Grant(*proxyprivs),
- davxml.Protected(),
- TwistedACLInheritable(),
- ),
- )
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ # DAV:read/DAV:read-current-user-privilege-set/DAV:write access for this principal's calendar-proxy-write users.
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(self._shareePrincipal.principalURL(), "calendar-proxy-write/"))),
+ davxml.Grant(*proxyprivs),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
- returnValue(davxml.ACL(*aces))
+ returnValue(davxml.ACL(*aces))
def validUserIDForShare(self, userid):
"""
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/stdconfig.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -79,6 +79,8 @@
"emailSuffix": None, # used only to synthesize email address
"filter": None, # additional filter for this type
"recordName": "userid", # uniquely identifies user records
+ "loginEnabledAttr" : "loginEnabled", # attribute controlling login
+ "loginEnabledValue" : "yes", # value of above attribute
},
"groups": {
"rdn": "ou=Group",
@@ -436,9 +438,10 @@
#
# Standard (or draft) WebDAV extensions
#
- "EnableAddMember" : True, # POST ;add-member extension
- "EnableSyncReport" : True, # REPORT collection-sync
- "EnableWellKnown" : True, # /.well-known resource
+ "EnableAddMember" : True, # POST ;add-member extension
+ "EnableSyncReport" : True, # REPORT collection-sync
+ "EnableWellKnown" : True, # /.well-known resource
+ "EnableCalendarQueryExtended" : True, # Extended calendar-query REPORT
#
# Non-standard CalDAV extensions
@@ -450,16 +453,16 @@
"TimezoneService" : { # New standard timezone service
"Enabled" : True, # Overall on/off switch
"Mode" : "primary", # Can be "primary" or "secondary"
- "BasePath" : None, # Path to zoneinfo - if None use default package path
+ "BasePath" : "", # Path to zoneinfo - if None use default package path
# secondary service MUST define its own writeable path
- "XMLInfoPath" : None, # Path to db cache info - if None use default package path
+ "XMLInfoPath" : "", # Path to db cache info - if None use default package path
# secondary service MUST define its own writeable path if
# not None
"SecondaryService" : {
# Only one of these should be used when a secondary service is used
- "Host" : None, # Domain/IP of secondary service to discover
- "URI" : None, # HTTP(s) URI to secondary service
+ "Host" : "", # Domain/IP of secondary service to discover
+ "URI" : "", # HTTP(s) URI to secondary service
"UpdateIntervalMinutes" : 24 * 60,
}
@@ -485,7 +488,7 @@
# CardDAV Features
"DirectoryAddressBook": {
- "Enabled": True,
+ "Enabled": False,
"type": "twistedcaldav.directory.opendirectorybacker.OpenDirectoryBackingService",
"params": directoryAddressBookBackingServiceDefaultParams["twistedcaldav.directory.opendirectorybacker.OpenDirectoryBackingService"],
"name": "directory",
@@ -495,7 +498,7 @@
"AnonymousDirectoryAddressBookAccess": False, # Anonymous users may access directory address book
"GlobalAddressBook": {
- "Enabled": True,
+ "Enabled": False,
"Name": "global-addressbook",
"EnableAnonymousReadAccess": False,
},
@@ -1061,6 +1064,19 @@
log.debug("Nav ACL: %s" % (configDict.ProvisioningResourceACL.toxml(),))
+ def principalObjects(urls):
+ for principalURL in urls:
+ yield davxml.Principal(davxml.HRef(principalURL))
+
+ # Should be sets, except WebDAVElement isn't hashable.
+ a = configDict.AdminPrincipalObjects = list(
+ principalObjects(configDict.AdminPrincipals))
+ b = configDict.ReadPrincipalObjects = list(
+ principalObjects(configDict.ReadPrincipals))
+ configDict.AllAdminPrincipalObjects = a + b
+
+
+
def _updateRejectClients(configDict):
#
# Compile RejectClients expressions for speed
@@ -1208,6 +1224,8 @@
compliance += customxml.calendarserver_sharing_compliance
# TODO: This is only needed whilst we do not support scheduling in shared calendars
compliance += customxml.calendarserver_sharing_no_scheduling_compliance
+ if configDict.EnableCalendarQueryExtended:
+ compliance += caldavxml.caldav_query_extended_compliance
else:
compliance = ()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/storebridge.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/storebridge.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -25,7 +25,11 @@
from twext.web2.dav.util import parentForURL, allDataFromStream, joinURL, davXMLFromStream
from twext.web2.http import HTTPError, StatusResponse, Response
from twext.web2.http_headers import ETag, MimeType
-from twext.web2.responsecode import FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED, BAD_REQUEST, OK
+from twext.web2.responsecode import (
+ FORBIDDEN, NO_CONTENT, NOT_FOUND, CREATED, CONFLICT, PRECONDITION_FAILED,
+ BAD_REQUEST, OK, INSUFFICIENT_STORAGE_SPACE
+)
+
from twext.web2.stream import ProducerStream, readStream, MemoryStream
from twisted.internet.defer import succeed, inlineCallbacks, returnValue, maybeDeferred
from twisted.internet.protocol import Protocol
@@ -43,13 +47,16 @@
from twistedcaldav.memcachelock import MemcacheLock, MemcacheLockTimeoutError
from twistedcaldav.method.put_addressbook_common import StoreAddressObjectResource
from twistedcaldav.method.put_common import StoreCalendarObjectResource
-from twistedcaldav.notifications import NotificationCollectionResource, NotificationResource
+from twistedcaldav.notifications import (
+ NotificationCollectionResource, NotificationResource
+)
from twistedcaldav.resource import CalDAVResource, GlobalAddressBookResource
from twistedcaldav.schedule import ScheduleInboxResource
from twistedcaldav.scheduling.implicit import ImplicitScheduler
from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
from txdav.base.propertystore.base import PropertyName
+from txdav.caldav.icalendarstore import QuotaExceeded
from txdav.common.icommondatastore import NoSuchObjectResourceError
from urlparse import urlsplit
import time
@@ -938,7 +945,7 @@
filteredaces = (yield self.inheritedACEsforChildren(request))
tzids = set()
- isowner = (yield self.isOwner(request, adminprincipals=True, readprincipals=True))
+ isowner = (yield self.isOwner(request))
accessPrincipal = (yield self.resourceOwnerPrincipal(request))
for name, uid, type in (yield maybeDeferred(self.index().bruteForceSearch)): #@UnusedVariable
@@ -955,9 +962,8 @@
continue
# Get the access filtered view of the data
- caldata = yield child.iCalendarTextFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
try:
- subcalendar = VCalendar.fromString(caldata)
+ subcalendar = yield child.iCalendarFiltered(isowner, accessPrincipal.principalUID() if accessPrincipal else "")
except ValueError:
continue
assert subcalendar.name() == "VCALENDAR"
@@ -1517,12 +1523,18 @@
creating = (self._newStoreAttachment is None)
if creating:
- self._newStoreAttachment = self._newStoreObject = (yield self._newStoreCalendarObject.createAttachmentWithName(
- self.attachmentName,
- ))
+ self._newStoreAttachment = self._newStoreObject = (
+ yield self._newStoreCalendarObject.createAttachmentWithName(
+ self.attachmentName))
t = self._newStoreAttachment.store(content_type)
yield readStream(request.stream, t.write)
- yield t.loseConnection()
+ try:
+ yield t.loseConnection()
+ except QuotaExceeded:
+ raise HTTPError(
+ ErrorResponse(INSUFFICIENT_STORAGE_SPACE,
+ (dav_namespace, "quota-not-exceeded"))
+ )
returnValue(CREATED if creating else NO_CONTENT)
@@ -1618,9 +1630,6 @@
return succeed(self._newStoreObject.size())
- def text(self):
- return self._newStoreObject.text()
-
def component(self):
return self._newStoreObject.component()
@@ -1630,9 +1639,9 @@
log.debug("Resource not found: %s" % (self,))
raise HTTPError(responsecode.NOT_FOUND)
- output = yield self.text()
+ output = yield self.component()
- response = Response(200, {}, output)
+ response = Response(200, {}, str(output))
response.headers.setHeader("content-type", self.contentType())
returnValue(response)
@@ -1709,66 +1718,45 @@
returnValue(NO_CONTENT)
-class _CalendarObjectMetaDataMixin(object):
+
+class _MetadataProperty(object):
"""
- Dynamically create the required meta-data for an object resource
+ A python property which can be set either on a _newStoreObject or on some
+ metadata if no new store object exists yet.
"""
- def _get_accessMode(self):
- return self._newStoreObject.accessMode if self._newStoreObject else self._metadata.get("accessMode", None)
+ def __init__(self, name):
+ self.name = name
- def _set_accessMode(self, value):
- if self._newStoreObject:
- self._newStoreObject.accessMode = value
- else:
- self._metadata["accessMode"] = value
- accessMode = property(_get_accessMode, _set_accessMode)
-
- def _get_isScheduleObject(self):
- return self._newStoreObject.isScheduleObject if self._newStoreObject else self._metadata.get("isScheduleObject", None)
-
- def _set_isScheduleObject(self, value):
- if self._newStoreObject:
- self._newStoreObject.isScheduleObject = value
+ def __get__(self, oself, type=None):
+ if oself._newStoreObject:
+ return getattr(oself._newStoreObject, self.name)
else:
- self._metadata["isScheduleObject"] = value
+ return oself._metadata.get(self.name, None)
- isScheduleObject = property(_get_isScheduleObject, _set_isScheduleObject)
- def _get_scheduleTag(self):
- return self._newStoreObject.scheduleTag if self._newStoreObject else self._metadata.get("scheduleTag", None)
-
- def _set_scheduleTag(self, value):
- if self._newStoreObject:
- self._newStoreObject.scheduleTag = value
+ def __set__(self, oself, value):
+ if oself._newStoreObject:
+ setattr(oself._newStoreObject, self.name, value)
else:
- self._metadata["scheduleTag"] = value
+ oself._metadata[self.name] = value
- scheduleTag = property(_get_scheduleTag, _set_scheduleTag)
- def _get_scheduleEtags(self):
- return self._newStoreObject.scheduleEtags if self._newStoreObject else self._metadata.get("scheduleEtags", None)
- def _set_scheduleEtags(self, value):
- if self._newStoreObject:
- self._newStoreObject.scheduleEtags = value
- else:
- self._metadata["scheduleEtags"] = value
+class _CalendarObjectMetaDataMixin(object):
+ """
+ Dynamically create the required meta-data for an object resource
+ """
- scheduleEtags = property(_get_scheduleEtags, _set_scheduleEtags)
+ accessMode = _MetadataProperty("accessMode")
+ isScheduleObject = _MetadataProperty("isScheduleObject")
+ scheduleTag = _MetadataProperty("scheduleTag")
+ scheduleEtags = _MetadataProperty("scheduleEtags")
+ hasPrivateComment = _MetadataProperty("hasPrivateComment")
- def _get_hasPrivateComment(self):
- return self._newStoreObject.hasPrivateComment if self._newStoreObject else self._metadata.get("hasPrivateComment", None)
- def _set_hasPrivateComment(self, value):
- if self._newStoreObject:
- self._newStoreObject.hasPrivateComment = value
- else:
- self._metadata["hasPrivateComment"] = value
- hasPrivateComment = property(_get_hasPrivateComment, _set_hasPrivateComment)
-
class CalendarObjectResource(_CalendarObjectMetaDataMixin, _CommonObjectResource):
"""
A resource wrapping a calendar object.
@@ -1808,7 +1796,11 @@
returnValue(txn)
- iCalendarText = _CommonObjectResource.text
+ @inlineCallbacks
+ def iCalendarText(self):
+ data = yield self.iCalendar()
+ returnValue(str(data))
+
iCalendar = _CommonObjectResource.component
@@ -1864,7 +1856,10 @@
isinbox = self._newStoreObject._calendar.name() == "inbox"
# Do If-Schedule-Tag-Match behavior first
- if not isinbox:
+ # Important: this should only ever be done when storeRemove is called
+ # directly as a result of an HTTP DELETE to ensure the proper If-
+ # header is used in this test.
+ if not isinbox and implicitly:
self.validIfScheduleMatch(request)
scheduler = None
@@ -2059,9 +2054,14 @@
_componentFromStream = VCard.fromString
- vCardText = _CommonObjectResource.text
+ @inlineCallbacks
+ def vCardText(self):
+ data = yield self.vCard()
+ returnValue(str(data))
+ vCard = _CommonObjectResource.component
+
class _NotificationChildHelper(object):
"""
Methods for things which are like notification objects.
Copied: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/AnotherEvent.ics (from rev 7695, CalendarServer/trunk/twistedcaldav/test/data/AnotherEvent.ics)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/AnotherEvent.ics (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/AnotherEvent.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_Yrok
+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
+BEGIN:VEVENT
+CREATED:20110516T031511Z
+UID:1C4B4547-9D99-446B-B1D7-135AE04A319E
+DTEND;TZID=America/New_Yrok:20110516T130000
+TRANSP:OPAQUE
+SUMMARY:Another Event
+DTSTART;TZID=America/New_Yrok:20110516T114500
+DTSTAMP:20110516T031514Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184A66-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184A66-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184A66-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3184A66-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020101
DTEND;VALUE=DATE:20020102
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20031231;BYMONTH=1
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184D26-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184D26-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3184D26-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3184D26-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020121
DTEND;VALUE=DATE:20020122
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040118;BYMONTH=1;BYDAY=3MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185326-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185326-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185326-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3185326-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020212
DTEND;VALUE=DATE:20020213
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31854DA-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31854DA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31854DA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
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
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31856AC-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31856AC-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31856AC-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C31856AC-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020218
DTEND;VALUE=DATE:20020219
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040215;BYMONTH=2;BYDAY=3MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318585A-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318585A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318585A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318585A-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020222
DTEND;VALUE=DATE:20020223
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040221;BYMONTH=2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185A14-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185A14-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185A14-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3185A14-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020317
DTEND;VALUE=DATE:20020318
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=3
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185BBD-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185BBD-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185BBD-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3185BBD-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020331
DTEND;VALUE=DATE:20020401
SUMMARY:Easter
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185D63-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185D63-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185D63-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3185D63-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020415
DTEND;VALUE=DATE:20020416
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185F20-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185F20-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3185F20-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3185F20-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020512
DTEND;VALUE=DATE:20020513
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=2SU
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31860C8-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31860C8-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31860C8-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C31860C8-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020527
DTEND;VALUE=DATE:20020528
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040530;BYMONTH=5;BYDAY=-1MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318627C-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318627C-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318627C-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318627C-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020529
DTEND;VALUE=DATE:20020530
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186426-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186426-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186426-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186426-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020616
DTEND;VALUE=DATE:20020617
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6;BYDAY=3SU
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31865E4-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31865E4-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31865E4-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C31865E4-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020704
DTEND;VALUE=DATE:20020705
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040703;BYMONTH=7
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186792-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186792-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186792-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186792-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020902
DTEND;VALUE=DATE:20020903
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040905;BYMONTH=9;BYDAY=1MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186938-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186938-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186938-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186938-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20021014
DTEND;VALUE=DATE:20021015
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20041010;BYMONTH=10;BYDAY=2MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186ADE-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186ADE-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186ADE-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186ADE-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20021027
DTEND;VALUE=DATE:20021028
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=-1SU
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186C96-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186C96-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186C96-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186C96-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20021031
DTEND;VALUE=DATE:20021101
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186E3A-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186E3A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186E3A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186E3A-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20021111
DTEND;VALUE=DATE:20021112
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20041110;BYMONTH=11
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186FE7-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186FE7-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3186FE7-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3186FE7-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20021128
DTEND;VALUE=DATE:20021129
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20041124;BYMONTH=11;BYDAY=4TH
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318719A-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318719A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318719A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318719A-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20021225
DTEND;VALUE=DATE:20021226
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20041224;BYMONTH=12
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3187343-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3187343-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3187343-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3187343-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20030420
DTEND;VALUE=DATE:20030421
SUMMARY:Easter
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188906-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188906-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188906-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3188906-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020407
DTEND;VALUE=DATE:20020408
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=4;BYDAY=1SU
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188B3A-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188B3A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188B3A-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3188B3A-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020202
DURATION:P1D
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188CFF-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188CFF-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188CFF-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3188CFF-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020401
DURATION:P1D
SEQUENCE:2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188EAA-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188EAA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3188EAA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3188EAA-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020506
DURATION:P1D
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20040502;BYMONTH=5;BYDAY=1MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189058-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189058-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189058-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189058-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020505
DURATION:P1D
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189203-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189203-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189203-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189203-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20020614
DURATION:P1D
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=6
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31893C2-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31893C2-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31893C2-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C31893C2-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20050327
DTEND;VALUE=DATE:20050328
SEQUENCE:3
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189572-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189572-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189572-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189572-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040401
DTEND;VALUE=DATE:20040402
RRULE:FREQ=YEARLY;INTERVAL=1
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189716-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189716-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189716-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189716-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040101
DTEND;VALUE=DATE:20040102
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31898D4-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31898D4-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C31898D4-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C31898D4-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040119
DTEND;VALUE=DATE:20040120
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=1;BYDAY=3MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189A88-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189A88-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189A88-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189A88-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040216
DTEND;VALUE=DATE:20040217
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2;BYDAY=3MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189C32-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189C32-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189C32-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189C32-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040222
DTEND;VALUE=DATE:20040223
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189DEC-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189DEC-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189DEC-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189DEC-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040411
DTEND;VALUE=DATE:20040412
SEQUENCE:2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189F94-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189F94-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C3189F94-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C3189F94-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20060416
DTEND;VALUE=DATE:20060417
SEQUENCE:2
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A148-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A148-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A148-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318A148-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040531
DTEND;VALUE=DATE:20040601
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=-1MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A2F3-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A2F3-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A2F3-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318A2F3-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040503
DURATION:P1D
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=5;BYDAY=1MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318A4BA-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040704
DTEND;VALUE=DATE:20040705
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=7
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A6E1-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A6E1-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A6E1-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318A6E1-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040906
DTEND;VALUE=DATE:20040907
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9;BYDAY=1MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A898-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A898-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318A898-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318A898-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20041011
DTEND;VALUE=DATE:20041012
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=10;BYDAY=2MO
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AA54-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AA54-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AA54-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318AA54-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20041111
DTEND;VALUE=DATE:20041112
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ABFE-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ABFE-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ABFE-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318ABFE-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20041125
DTEND;VALUE=DATE:20041127
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=11;BYDAY=4TH
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ADAA-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ADAA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318ADAA-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318ADAA-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20041225
DTEND;VALUE=DATE:20041226
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=12
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AF53-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AF53-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318AF53-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318AF53-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20041231
DTEND;VALUE=DATE:20050101
RRULE:FREQ=YEARLY;INTERVAL=1
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B108-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B108-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B108-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318B108-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20040912
DTEND;VALUE=DATE:20040913
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=9;BYDAY=2SU
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B2D2-1ED0-11D9-A5E0-000A958A3252.ics
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B2D2-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/Holidays/C318B2D2-1ED0-11D9-A5E0-000A958A3252.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -4,6 +4,7 @@
VERSION:2.0
BEGIN:VEVENT
UID:C318B2D2-1ED0-11D9-A5E0-000A958A3252
+DTSTAMP:20041015T171054Z
DTSTART;VALUE=DATE:20041102
DTEND;VALUE=DATE:20041103
DESCRIPTION:Every four years on the first Tuesday after the first Monday in
Copied: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/OneEvent.ics (from rev 7695, CalendarServer/trunk/twistedcaldav/test/data/OneEvent.ics)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/OneEvent.ics (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/OneEvent.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:America/New_Yrok
+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
+BEGIN:VEVENT
+CREATED:20110516T031505Z
+UID:89E0257D-3522-4423-B94E-60FF10BDAAFA
+DTEND;TZID=America/New_Yrok:20110516T110000
+TRANSP:OPAQUE
+SUMMARY:One Event
+DTSTART;TZID=America/New_Yrok:20110516T094500
+DTSTAMP:20110516T031511Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
Copied: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/ThirdEvent.ics (from rev 7695, CalendarServer/trunk/twistedcaldav/test/data/ThirdEvent.ics)
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/ThirdEvent.ics (rev 0)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/data/ThirdEvent.ics 2011-06-30 20:44:13 UTC (rev 7697)
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+PRODID:-//Apple Inc.//iCal 4.0.4//EN
+CALSCALE:GREGORIAN
+BEGIN:VTIMEZONE
+TZID:US/Pacific
+BEGIN:DAYLIGHT
+TZOFFSETFROM:-0800
+RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU
+DTSTART:20070311T020000
+TZNAME:PDT
+TZOFFSETTO:-0700
+END:DAYLIGHT
+BEGIN:STANDARD
+TZOFFSETFROM:-0700
+RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU
+DTSTART:20071104T020000
+TZNAME:PST
+TZOFFSETTO:-0800
+END:STANDARD
+END:VTIMEZONE
+BEGIN:VEVENT
+CREATED:20110516T033401Z
+UID:39F43724-3103-4379-9A0A-11FAD4FF542A
+DTEND;TZID=US/Pacific:20110516T114500
+TRANSP:OPAQUE
+SUMMARY:Third Event
+DTSTART;TZID=US/Pacific:20110516T104500
+DTSTAMP:20110516T033404Z
+SEQUENCE:2
+END:VEVENT
+END:VCALENDAR
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_calendarquery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_calendarquery.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_calendarquery.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -34,7 +34,6 @@
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase
from twisted.internet.defer import inlineCallbacks, returnValue
-from twistedcaldav.memcacher import Memcacher
from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
@@ -355,9 +354,6 @@
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(Memcacher, "allowTestCache", True)
self.calendarStore = yield buildStore(self, StubNotifierFactory())
yield super(DatabaseQueryTests, self).setUp()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_icalendar.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_icalendar.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -26,6 +26,7 @@
from pycalendar.datetime import PyCalendarDateTime
from pycalendar.timezone import PyCalendarTimezone
+from twistedcaldav.ical import iCalendarProductID
from pycalendar.duration import PyCalendarDuration
class iCalendar (twistedcaldav.test.util.TestCase):
@@ -50,6 +51,20 @@
else:
SkipTest("test unimplemented")
+
+ def test_newCalendar(self):
+ """
+ L{Component.newCalendar} creates a new VCALENDAR L{Component} with
+ appropriate version and product identifiers, and no subcomponents.
+ """
+ calendar = Component.newCalendar()
+ version = calendar.getProperty("VERSION")
+ prodid = calendar.getProperty("PRODID")
+ self.assertEqual(version.value(), "2.0")
+ self.assertEqual(prodid.value(), iCalendarProductID)
+ self.assertEqual(list(calendar.subcomponents()), [])
+
+
def test_component_equality(self):
# for filename in (
# os.path.join(self.data_dir, "Holidays", "C318A4BA-1ED0-11D9-A5E0-000A958A3252.ics"),
@@ -72,6 +87,7 @@
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -89,6 +105,7 @@
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -110,6 +127,7 @@
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -123,6 +141,7 @@
RECURRENCE-ID:20080602T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -138,6 +157,7 @@
RECURRENCE-ID:20080602T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -147,6 +167,7 @@
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -168,6 +189,7 @@
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -195,6 +217,7 @@
UID:12345-67890
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
SUMMARY:Test
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
@@ -233,9 +256,13 @@
CalDAV resource validation.
"""
calendar = Component.fromStream(file(os.path.join(self.data_dir, "Holidays.ics")))
- try: calendar.validateForCalDAV()
- except ValueError: pass
- else: self.fail("Monolithic iCalendar shouldn't validate for CalDAV")
+ try:
+ calendar.validCalendarData()
+ calendar.validCalendarForCalDAV(methodAllowed=False)
+ except ValueError:
+ pass
+ else:
+ self.fail("Monolithic iCalendar shouldn't validate for CalDAV")
resource_dir = os.path.join(self.data_dir, "Holidays")
for filename in resource_dir:
@@ -243,8 +270,11 @@
filename = os.path.join(resource_dir, filename)
calendar = Component.fromStream(file(filename))
- try: calendar.validateForCalDAV()
- except ValueError: self.fail("Resource iCalendar %s didn't validate for CalDAV" % (filename,))
+ try:
+ calendar.validCalendarData()
+ calendar.validCalendarForCalDAV(methodAllowed=False)
+ except ValueError:
+ self.fail("Resource iCalendar %s didn't validate for CalDAV" % (filename,))
def test_component_validate_and_fix(self):
"""
@@ -252,6 +282,7 @@
"""
data = """BEGIN:VCALENDAR
VERSION:2.0
+PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
BEGIN:DAYLIGHT
@@ -283,20 +314,24 @@
"""
# Ensure it starts off invalid
calendar = Component.fromString(data)
- try: calendar.validateComponentsForCalDAV(False)
- except InvalidICalendarDataError: pass
- else: self.fail("Shouldn't validate for CalDAV")
+ try:
+ calendar.validCalendarData(doFix=False)
+ except InvalidICalendarDataError:
+ pass
+ else:
+ self.fail("Shouldn't validate for CalDAV")
# Fix it
- calendar.validateComponentsForCalDAV(False, fix=True)
+ calendar.validCalendarData(doFix=True)
self.assertTrue("RRULE:FREQ=DAILY;UNTIL=20110121T203000Z\r\n"
in str(calendar))
# Now it should pass without fixing
- calendar.validateComponentsForCalDAV(False, fix=False)
+ calendar.validCalendarData(doFix=False)
data = """BEGIN:VCALENDAR
VERSION:2.0
+PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
BEGIN:VTIMEZONE
TZID:America/Los_Angeles
BEGIN:DAYLIGHT
@@ -328,16 +363,19 @@
"""
# Ensure it starts off invalid
calendar = Component.fromString(data)
- try: calendar.validateComponentsForCalDAV(False)
- except InvalidICalendarDataError: pass
- else: self.fail("Shouldn't validate for CalDAV")
+ try:
+ calendar.validCalendarData(doFix=False)
+ except InvalidICalendarDataError:
+ pass
+ else:
+ self.fail("Shouldn't validate for CalDAV")
# Fix it
- calendar.validateComponentsForCalDAV(False, fix=True)
+ calendar.validCalendarData(doFix=True)
self.assertTrue("RRULE:FREQ=DAILY;UNTIL=20110131\r\n" in str(calendar))
# Now it should pass without fixing
- calendar.validateComponentsForCalDAV(False, fix=False)
+ calendar.validCalendarData(doFix=False)
def test_component_timeranges(self):
@@ -476,11 +514,13 @@
data = """BEGIN:VCALENDAR
VERSION:2.0
+PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20071114T000000Z
END:VEVENT
END:VCALENDAR
"""
@@ -492,11 +532,13 @@
data = """BEGIN:VCALENDAR
VERSION:2.0
+DTSTART:20071114T000000Z
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20071114T000000Z
END:VEVENT
END:VCALENDAR
"""
@@ -513,6 +555,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -526,6 +569,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
END:VEVENT
@@ -541,6 +585,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ORGANIZER:mailto:user2 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -555,6 +600,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
RRULE:FREQ=YEARLY
@@ -563,6 +609,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
END:VEVENT
@@ -579,6 +626,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
RRULE:FREQ=YEARLY
@@ -587,6 +635,7 @@
UID:12345-67890
RECURRENCE-ID:20091114T000000Z
DTSTART:20071114T020000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user3 at example.com
ATTENDEE:mailto:user2 at example.com
END:VEVENT
@@ -603,6 +652,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
RRULE:FREQ=YEARLY
@@ -611,6 +661,7 @@
UID:12345-67890
RECURRENCE-ID:20091114T000000Z
DTSTART:20071114T020000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user3 at example.com
ORGANIZER:mailto:user4 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -636,6 +687,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -648,6 +700,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
END:VEVENT
@@ -664,6 +717,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
ATTENDEE:mailto:user3 at example.com
@@ -682,6 +736,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
RRULE:FREQ=YEARLY
@@ -690,6 +745,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE;SCHEDULE-AGENT=NONE:mailto:user2 at example.com
ATTENDEE;SCHEDULE-AGENT=CLIENT:mailto:user3 at example.com
@@ -709,6 +765,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE;SCHEDULE-AGENT=NONE:mailto:user2 at example.com
ATTENDEE:mailto:user3 at example.com
@@ -726,6 +783,7 @@
BEGIN:VEVENT
UID:12345-67890
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE;SCHEDULE-AGENT=SERVER:mailto:user2 at example.com
RRULE:FREQ=YEARLY
@@ -734,6 +792,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE;SCHEDULE-AGENT=NONE:mailto:user2 at example.com
ATTENDEE;SCHEDULE-AGENT=CLIENT:mailto:user3 at example.com
@@ -763,6 +822,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -775,6 +835,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE;SCHEDULE-STATUS=2.0:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -796,6 +857,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE;SCHEDULE-STATUS=5.0:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -808,6 +870,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE;SCHEDULE-STATUS=2.0:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -829,6 +892,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -841,6 +905,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;SCHEDULE-STATUS=2.0:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -862,6 +927,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;SCHEDULE-STATUS=5.0:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -874,6 +940,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user01 at example.com
ATTENDEE:mailto:user02 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER;SCHEDULE-STATUS=2.0:mailto:user01 at example.com
END:VEVENT
END:VCALENDAR
@@ -902,6 +969,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -911,6 +979,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
REQUEST-STATUS:2.0;Success
END:VEVENT
END:VCALENDAR
@@ -924,12 +993,14 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-1
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T020000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -939,6 +1010,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
REQUEST-STATUS:2.0;Success
RRULE:FREQ=DAILY
END:VEVENT
@@ -946,6 +1018,7 @@
UID:12345-67890-1
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T020000Z
+DTSTAMP:20080601T120000Z
REQUEST-STATUS:2.0;Success
END:VEVENT
END:VCALENDAR
@@ -969,6 +1042,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -979,6 +1053,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -993,6 +1068,7 @@
BEGIN:VEVENT
UID:12345-67890-2
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1014,6 +1090,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1026,6 +1103,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1042,6 +1120,7 @@
UID:12345-67890-4
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1064,6 +1143,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1072,6 +1152,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1084,6 +1165,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1092,6 +1174,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1108,6 +1191,7 @@
UID:12345-67890-4
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1116,6 +1200,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1138,6 +1223,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1145,6 +1231,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1157,6 +1244,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
EXDATE:20081114T000000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
@@ -1175,6 +1263,7 @@
UID:12345-67890-4
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1182,6 +1271,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1203,6 +1293,7 @@
BEGIN:VEVENT
UID:12345-67890-3
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1211,6 +1302,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1224,6 +1316,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1239,6 +1332,7 @@
BEGIN:VEVENT
UID:12345-67890-4
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1247,6 +1341,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1268,6 +1363,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1278,6 +1374,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1292,6 +1389,7 @@
BEGIN:VEVENT
UID:12345-67890-2
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1313,6 +1411,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1325,6 +1424,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1341,6 +1441,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE;SCHEDULE-AGENT=SERVER:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1353,6 +1454,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE;SCHEDULE-AGENT=SERVER:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1369,6 +1471,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE;SCHEDULE-AGENT=CLIENT:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1391,6 +1494,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE;SCHEDULE-AGENT=NONE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1422,6 +1526,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1431,6 +1536,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1446,6 +1552,7 @@
UID:12345-67890-2
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1456,6 +1563,7 @@
BEGIN:VEVENT
UID:12345-67890-2
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1472,6 +1580,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1483,6 +1592,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1500,6 +1610,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
ATTENDEE:mailto:user3 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1510,6 +1621,7 @@
BEGIN:VEVENT
UID:12345-67890-4
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1527,6 +1639,7 @@
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
ATTENDEE:mailto:user3 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1538,6 +1651,7 @@
UID:12345-67890-5
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1564,6 +1678,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
SUMMARY:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
@@ -1577,6 +1692,7 @@
BEGIN:VEVENT
UID:12345-67890-1
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1594,6 +1710,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
BEGIN:VALARM
@@ -1607,6 +1724,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1618,6 +1736,7 @@
BEGIN:VEVENT
UID:12345-67890-3
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
BEGIN:VALARM
ACTION:DISPLAY
@@ -1629,6 +1748,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1655,6 +1775,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
SUMMARY:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
ORGANIZER:mailto:user1 at example.com
@@ -1668,6 +1789,7 @@
BEGIN:VEVENT
UID:12345-67890-1
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1685,6 +1807,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
BEGIN:VALARM
@@ -1698,6 +1821,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1709,6 +1833,7 @@
BEGIN:VEVENT
UID:12345-67890-3
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
BEGIN:VALARM
@@ -1721,6 +1846,7 @@
UID:12345-67890
RECURRENCE-ID:20081114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1746,6 +1872,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1755,6 +1882,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1768,6 +1896,7 @@
BEGIN:VEVENT
UID:12345-67890-2
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
BEGIN:VALARM
ACTION:DISPLAY
DESCRIPTION:Test
@@ -1782,6 +1911,7 @@
BEGIN:VEVENT
UID:12345-67890-2
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -1796,6 +1926,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
BEGIN:VALARM
@@ -1809,6 +1940,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1820,6 +1952,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1828,6 +1961,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1843,6 +1977,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
BEGIN:VALARM
@@ -1856,6 +1991,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
BEGIN:VALARM
ACTION:DISPLAY
@@ -1872,6 +2008,7 @@
UID:12345-67890-3
DTSTART:20071114T000000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
RRULE:FREQ=YEARLY
END:VEVENT
@@ -1880,6 +2017,7 @@
RECURRENCE-ID:20081114T000000Z
DTSTART:20071114T010000Z
ATTENDEE:mailto:user2 at example.com
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user1 at example.com
END:VEVENT
END:VCALENDAR
@@ -1903,6 +2041,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
@@ -1918,6 +2057,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
@@ -1937,6 +2077,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=2
RDATE:20071116T010000Z
@@ -1958,6 +2099,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=3
EXDATE:20071115T000000Z
@@ -1978,6 +2120,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=3
EXDATE:20071114T000000Z
@@ -1998,6 +2141,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
@@ -2005,6 +2149,7 @@
UID:12345-67890-1
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
@@ -2024,6 +2169,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
BEGIN:VEVENT
@@ -2031,6 +2177,7 @@
RECURRENCE-ID:20071115T010000Z
DTSTART:20071115T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2046,6 +2193,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
BEGIN:VEVENT
@@ -2053,6 +2201,7 @@
RECURRENCE-ID:20071115T010000Z
DTSTART:20071115T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2086,6 +2235,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2101,6 +2251,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2116,6 +2267,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
@@ -2123,6 +2275,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2138,6 +2291,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
@@ -2145,6 +2299,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2160,6 +2315,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
@@ -2167,6 +2323,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2182,6 +2339,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
@@ -2189,6 +2347,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2204,6 +2363,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
@@ -2211,6 +2371,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2226,12 +2387,14 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
UID:12345-67890-1
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
+DTSTAMP:20080601T120000Z
DURATION:PT1H
END:VEVENT
END:VCALENDAR
@@ -2257,6 +2420,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:True
END:VEVENT
END:VCALENDAR
@@ -2268,6 +2432,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM2:True
END:VEVENT
END:VCALENDAR
@@ -2279,6 +2444,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:True
X-ITEM2:True
END:VEVENT
@@ -2295,6 +2461,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:True
END:VEVENT
END:VCALENDAR
@@ -2306,6 +2473,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM2:True
X-ITEM3:True
END:VEVENT
@@ -2318,6 +2486,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:True
X-ITEM2:True
X-ITEM3:True
@@ -2335,6 +2504,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:True
END:VEVENT
END:VCALENDAR
@@ -2346,6 +2516,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM2:True
X-ITEM1:False
END:VEVENT
@@ -2358,6 +2529,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:True
X-ITEM2:True
X-ITEM1:False
@@ -2375,6 +2547,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
X-ITEM1:True
END:VEVENT
@@ -2383,6 +2556,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:False
END:VEVENT
END:VCALENDAR
@@ -2394,6 +2568,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
X-ITEM2:True
END:VEVENT
@@ -2402,6 +2577,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM2:False
END:VEVENT
END:VCALENDAR
@@ -2413,6 +2589,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
X-ITEM1:True
X-ITEM2:True
@@ -2422,6 +2599,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:False
X-ITEM2:False
END:VEVENT
@@ -2438,6 +2616,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
X-ITEM1:True
END:VEVENT
@@ -2446,6 +2625,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:False
END:VEVENT
END:VCALENDAR
@@ -2457,6 +2637,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
X-ITEM2:True
END:VEVENT
@@ -2469,6 +2650,7 @@
UID:12345-67890-1
DTSTART:20071114T000000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
X-ITEM1:True
X-ITEM2:True
@@ -2478,6 +2660,7 @@
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
DURATION:PT1H
+DTSTAMP:20080601T120000Z
X-ITEM1:False
X-ITEM2:True
END:VEVENT
@@ -2505,6 +2688,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114T000000Z
+DTSTAMP:20080601T120000Z
SEQUENCE:0
END:VEVENT
END:VCALENDAR
@@ -2515,6 +2699,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2527,6 +2712,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114T000000Z
+DTSTAMP:20080601T120000Z
TRANSP:OPAQUE
ORGANIZER:mailto:user01 at example.com
ATTENDEE;RSVP=TRUE;PARTSTAT=NEEDS-ACTION:mailto:user02 at example.com
@@ -2542,6 +2728,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
ATTENDEE;RSVP=TRUE:mailto:user02 at example.com
ATTENDEE:mailto:user03 at example.com
@@ -2559,6 +2746,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=1;BYDAY=MO,WE,FR
TRANSP:OPAQUE
ORGANIZER:mailto:user01 at example.com
@@ -2575,6 +2763,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
ATTENDEE;RSVP=TRUE:mailto:user02 at example.com
ATTENDEE:mailto:user03 at example.com
@@ -2610,6 +2799,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;TZID=US/Pacific:20071114T000000
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY;WKST=SU;INTERVAL=1;BYDAY=MO,WE,FR
TRANSP:OPAQUE
ORGANIZER:mailto:user01 at example.com
@@ -2643,6 +2833,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;_TZID=US/Pacific:20071114T080000Z
+DTSTAMP:20080601T120000Z
ORGANIZER:mailto:user01 at example.com
ATTENDEE;RSVP=TRUE:mailto:user02 at example.com
ATTENDEE:mailto:user03 at example.com
@@ -2675,6 +2866,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2684,6 +2876,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2697,6 +2890,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
ATTACH:http://example.com/file.txt
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2707,6 +2901,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
ATTACH:http://example.com/file.txt
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2720,6 +2915,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
ATTACH:http://example.com/calendars/user.dropbox/file.txt
+DTSTAMP:20080601T120000Z
X-APPLE-DROPBOX:/calendars/user.dropbox
END:VEVENT
END:VCALENDAR
@@ -2730,6 +2926,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
+DTSTAMP:20080601T120000Z
X-APPLE-DROPBOX:/calendars/user.dropbox
END:VEVENT
END:VCALENDAR
@@ -2744,6 +2941,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
ATTACH:http://example.com/calendars/user.dropbox/file.txt
+DTSTAMP:20080601T120000Z
X-APPLE-DROPBOX:/calendars/user1.dropbox
END:VEVENT
END:VCALENDAR
@@ -2755,6 +2953,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE-TIME:20071114
ATTACH:http://example.com/calendars/user.dropbox/file.txt
+DTSTAMP:20080601T120000Z
X-APPLE-DROPBOX:/calendars/user1.dropbox
END:VEVENT
END:VCALENDAR
@@ -2783,6 +2982,7 @@
UID:12345-67890-1
DTSTART:20090101T000000Z
DTEND:20090102T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -2797,6 +2997,7 @@
UID:12345-67890-1
DTSTART:20090101T000000Z
DTEND:20090102T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY;COUNT=2
END:VEVENT
END:VCALENDAR
@@ -2812,6 +3013,7 @@
UID:12345-67890-1
DTSTART:20090101T000000Z
DTEND:20090102T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY;UNTIL=20090108T000000Z
END:VEVENT
END:VCALENDAR
@@ -2827,6 +3029,7 @@
UID:12345-67890-1
DTSTART:20090101T000000Z
DTEND:20090102T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -2852,6 +3055,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -2862,6 +3066,7 @@
RECURRENCE-ID:20090102T080000Z
DTSTART:20090102T080000Z
DTEND:20090102T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -2874,6 +3079,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z
END:VEVENT
@@ -2885,6 +3091,7 @@
RECURRENCE-ID:20090102T180000Z
DTSTART:20090102T180000Z
DTEND:20090102T190000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -2897,6 +3104,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z,20090103T180000Z
RDATE:20090104T180000Z
@@ -2909,6 +3117,7 @@
RECURRENCE-ID:20090103T180000Z
DTSTART:20090103T180000Z
DTEND:20090103T190000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -2921,6 +3130,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -2937,6 +3147,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z
END:VEVENT
@@ -2954,6 +3165,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z,20090103T180000Z
RDATE:20090104T180000Z
@@ -2972,6 +3184,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE:20090101
DTEND;VALUE=DATE:20090102
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
@@ -2982,6 +3195,7 @@
RECURRENCE-ID;VALUE=DATE:20090108
DTSTART;VALUE=DATE:20090108
DTEND;VALUE=DATE:20090109
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -2994,6 +3208,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE:20090101
DTEND;VALUE=DATE:20090102
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY
RDATE;VALUE=DATE:20090103
END:VEVENT
@@ -3005,6 +3220,7 @@
RECURRENCE-ID;VALUE=DATE:20090103
DTSTART;VALUE=DATE:20090103
DTEND;VALUE=DATE:20090104
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3017,6 +3233,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE:20090101
DTEND;VALUE=DATE:20090102
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY
RDATE;VALUE=DATE:20090103,20090110
RDATE;VALUE=DATE:20090118
@@ -3029,6 +3246,7 @@
RECURRENCE-ID;VALUE=DATE:20090110
DTSTART;VALUE=DATE:20090110
DTEND;VALUE=DATE:20090111
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3041,6 +3259,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE:20090101
DTEND;VALUE=DATE:20090102
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
@@ -3057,6 +3276,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE:20090101
DTEND;VALUE=DATE:20090102
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY
RDATE;VALUE=DATE:20090104
END:VEVENT
@@ -3074,6 +3294,7 @@
UID:12345-67890-1
DTSTART;VALUE=DATE:20090101
DTEND;VALUE=DATE:20090102
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY
RDATE;VALUE=DATE:20090104,20090111
RDATE;VALUE=DATE:20090118
@@ -3103,6 +3324,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -3117,6 +3339,7 @@
RECURRENCE-ID:20090102T080000Z
DTSTART:20090102T080000Z
DTEND:20090102T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
"""BEGIN:VEVENT
@@ -3124,6 +3347,7 @@
RECURRENCE-ID:20090104T080000Z
DTSTART:20090104T080000Z
DTEND:20090104T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3137,6 +3361,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z
END:VEVENT
@@ -3152,6 +3377,7 @@
RECURRENCE-ID:20090102T180000Z
DTSTART:20090102T180000Z
DTEND:20090102T190000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
"""BEGIN:VEVENT
@@ -3159,6 +3385,7 @@
RECURRENCE-ID:20090104T080000Z
DTSTART:20090104T080000Z
DTEND:20090104T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3172,6 +3399,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z,20090103T180000Z
RDATE:20090104T180000Z
@@ -3188,6 +3416,7 @@
RECURRENCE-ID:20090103T180000Z
DTSTART:20090103T180000Z
DTEND:20090103T190000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
"""BEGIN:VEVENT
@@ -3195,6 +3424,7 @@
RECURRENCE-ID:20090105T080000Z
DTSTART:20090105T080000Z
DTEND:20090105T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3208,6 +3438,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -3223,6 +3454,7 @@
RECURRENCE-ID:20090103T080000Z
DTSTART:20090103T080000Z
DTEND:20090103T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3236,6 +3468,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z
END:VEVENT
@@ -3252,6 +3485,7 @@
RECURRENCE-ID:20090103T080000Z
DTSTART:20090103T080000Z
DTEND:20090103T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3265,6 +3499,7 @@
UID:12345-67890-1
DTSTART:20090101T080000Z
DTEND:20090101T090000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20090102T180000Z,20090103T180000Z
RDATE:20090104T180000Z
@@ -3282,6 +3517,7 @@
RECURRENCE-ID:20090103T080000Z
DTSTART:20090103T080000Z
DTEND:20090103T090000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
""",
),
@@ -3306,6 +3542,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3319,6 +3556,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY;COUNT=2
END:VEVENT
END:VCALENDAR
@@ -3333,6 +3571,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY;UNTIL=20071128T000000Z
END:VEVENT
END:VCALENDAR
@@ -3347,6 +3586,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY;COUNT=2000
END:VEVENT
END:VCALENDAR
@@ -3357,6 +3597,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:COUNT=400;FREQ=WEEKLY
END:VEVENT
END:VCALENDAR
@@ -3370,6 +3611,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY;UNTIL=20471128T000000Z
END:VEVENT
END:VCALENDAR
@@ -3380,6 +3622,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:COUNT=400;FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -3393,6 +3636,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=WEEKLY;UNTIL=20071128T000000Z
END:VEVENT
END:VCALENDAR
@@ -3407,6 +3651,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -3417,6 +3662,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:COUNT=400;FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -3452,6 +3698,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3469,6 +3716,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RDATE:20091004T000000Z
END:VEVENT
END:VCALENDAR
@@ -3488,6 +3736,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
END:VCALENDAR
@@ -3508,6 +3757,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20091004T010000Z
END:VEVENT
@@ -3530,6 +3780,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20091004T010000Z
EXDATE:20091003T000000Z
@@ -3554,6 +3805,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
END:VEVENT
BEGIN:VEVENT
@@ -3580,6 +3832,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
RRULE:FREQ=DAILY
RDATE:20071115T010000Z
END:VEVENT
@@ -3587,6 +3840,7 @@
UID:12345-67890-1
RECURRENCE-ID:20071115T010000Z
DTSTART:20071115T020000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3609,6 +3863,7 @@
UID:12345-67890-1
RECURRENCE-ID:20071115T000000Z
DTSTART:20071115T010000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3627,6 +3882,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3642,6 +3898,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3657,6 +3914,7 @@
BEGIN:VEVENT
UID:12345-67890-1
DTSTART:20071114T000000Z
+DTSTAMP:20080601T120000Z
END:VEVENT
END:VCALENDAR
""",
@@ -3769,11 +4027,12 @@
for text in invalid:
calendar = Component.fromString(text)
- self.assertRaises(InvalidICalendarDataError, calendar.validateForCalDAV)
+ self.assertRaises(InvalidICalendarDataError, calendar.validCalendarData, doFix=False)
for text in valid:
calendar = Component.fromString(text)
try:
- calendar.validateForCalDAV()
+ calendar.validCalendarData()
+ calendar.validCalendarForCalDAV(methodAllowed=False)
except:
self.fail("Valid calendar should validate")
@@ -3783,6 +4042,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -3826,6 +4086,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -3851,6 +4112,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -3888,6 +4150,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -3933,6 +4196,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -3964,6 +4228,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -4009,6 +4274,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -4063,6 +4329,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -4074,6 +4341,7 @@
BEGIN:VEVENT
UID:12345-67890
RECURRENCE-ID:20080602T120000Z
+DTSTAMP:20080601T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
@@ -4117,6 +4385,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -4127,6 +4396,7 @@
BEGIN:VEVENT
UID:12345-67890
RECURRENCE-ID:20080602T120000Z
+DTSTAMP:20080601T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
@@ -4193,6 +4463,7 @@
PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20080601T120000Z
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
ATTENDEE:mailto:user1 at example.com
@@ -4203,6 +4474,7 @@
BEGIN:VEVENT
UID:12345-67890
RECURRENCE-ID:20080602T120000Z
+DTSTAMP:20080601T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
ATTENDEE:mailto:user1 at example.com
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_mail.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_mail.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_mail.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -25,6 +25,7 @@
from twistedcaldav.mail import MailHandler
from twistedcaldav.mail import MailGatewayTokensDatabase
import os
+import datetime
def echo(*args):
@@ -38,6 +39,32 @@
self.handler = MailHandler(dataRoot=":memory:")
self.dataDir = os.path.join(os.path.dirname(__file__), "data", "mail")
+
+ def test_purge(self):
+ """
+ Ensure that purge( ) cleans out old tokens
+ """
+
+ # Insert an "old" token
+ token = "test_token"
+ organizer = "me at example.com"
+ attendee = "you at example.com"
+ icaluid = "123"
+ pastDate = datetime.date(2009,1,1)
+ self.handler.db._db_execute(
+ """
+ insert into TOKENS (TOKEN, ORGANIZER, ATTENDEE, ICALUID, DATESTAMP)
+ values (:1, :2, :3, :4, :5)
+ """, token, organizer, attendee, icaluid, pastDate
+ )
+ self.handler.db._db_commit()
+
+ # purge, and make sure we don't see that token anymore
+ self.handler.purge()
+ retrieved = self.handler.db.getToken(organizer, attendee, icaluid)
+ self.assertEquals(retrieved, None)
+
+
def test_iconPath(self):
iconPath = self.handler.getIconPath({'day':'1', 'month':'1'}, False, language='en')
iconDir = "/usr/share/caldavd/share/date_icons"
@@ -216,28 +243,28 @@
data = (
# Initial invite
(
- """BEGIN:VCALENDAR
+ u"""BEGIN:VCALENDAR
VERSION:2.0
METHOD:REQUEST
BEGIN:VEVENT
UID:CFDD5E46-4F74-478A-9311-B3FF905449C3
DTSTART:20100325T154500Z
DTEND:20100325T164500Z
-ATTENDEE;CN=The Attendee;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:attendee at example.com
+ATTENDEE;CN=Th\xe9 Attendee;CUTYPE=INDIVIDUAL;PARTSTAT=NEEDS-ACTION;RSVP=TRUE:mailto:attendee at example.com
ATTENDEE;CN=The Organizer;CUTYPE=INDIVIDUAL;EMAIL=organizer at example.com;PARTSTAT=ACCEPTED:urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A
ORGANIZER;CN=The Organizer;EMAIL=organizer at example.com:urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A
-SUMMARY:testing outbound( )
+SUMMARY:t\xe9sting outbound( )
END:VEVENT
END:VCALENDAR
""",
"CFDD5E46-4F74-478A-9311-B3FF905449C3",
- "mailto:organizer at example.com",
+ "urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A",
"mailto:attendee at example.com",
"new",
"organizer at example.com",
"The Organizer",
[
- (u'The Attendee', u'attendee at example.com'),
+ (u'Th\xe9 Attendee', u'attendee at example.com'),
(u'The Organizer', u'organizer at example.com')
],
"The Organizer <organizer at example.com>",
@@ -261,7 +288,7 @@
END:VCALENDAR
""",
"CFDD5E46-4F74-478A-9311-B3FF905449C3",
- "mailto:organizer at example.com",
+ "urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A",
"mailto:attendee at example.com",
"update",
"organizer at example.com",
@@ -290,7 +317,7 @@
END:VCALENDAR
""",
None,
- "mailto:attendee at example.com",
+ "urn:uuid:C3B38B00-4166-11DD-B22C-A07C87E02F6A",
"mailto:organizer at example.com",
"reply",
"organizer at example.com",
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_multiget.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_multiget.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_multiget.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -121,6 +121,7 @@
UID:good
DTSTART;VALUE=DATE:20020101
DTEND;VALUE=DATE:20020102
+DTSTAMP:20020101T121212Z
RRULE:FREQ=YEARLY;INTERVAL=1;UNTIL=20031231;BYMONTH=1
SUMMARY:New Year's Day
END:VEVENT
@@ -134,6 +135,7 @@
UID:bad
DTSTART;VALUE=DATE:20020214
DTEND;VALUE=DATE:20020215
+DTSTAMP:20020101T121212Z
RRULE:FREQ=YEARLY;INTERVAL=1;BYMONTH=2
SUMMARY:Valentine's Day
END:VEVENT
@@ -154,6 +156,7 @@
UID:bad
DTSTART;VALUE=DATE:20020214
DTEND;VALUE=DATE:20020
+DTSTAMP:20020101T121212Z
END:VCALENDAR
""".replace("\n", "\r\n"))
f.close
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_resource.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_resource.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2008 Apple Inc. All rights reserved.
+# Copyright (c) 2008-2011 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.
@@ -14,15 +14,24 @@
# limitations under the License.
##
-from twistedcaldav.resource import CalDAVResource, CommonHomeResource, CalendarHomeResource, AddressBookHomeResource
+from twext.web2.dav import davxml
+from twext.web2.dav.davxml import Principal
+from twext.web2.dav.davxml import Unauthenticated
+from twext.web2.dav.element.rfc2518 import HRef
+from twext.web2.http import HTTPError
+from twext.web2.test.test_server import SimpleRequest
+from twisted.internet.defer import inlineCallbacks
+
+from twistedcaldav import carddavxml
+from twistedcaldav.config import config
+from twistedcaldav.resource import CalDAVResource, CommonHomeResource, \
+ CalendarHomeResource, AddressBookHomeResource
from twistedcaldav.test.util import InMemoryPropertyStore
from twistedcaldav.test.util import TestCase
-from twistedcaldav.config import config
+from twistedcaldav.test.util import patchConfig
-from twisted.internet.defer import inlineCallbacks
-
class StubProperty(object):
def qname(self):
return "StubQnamespace", "StubQname"
@@ -62,6 +71,7 @@
self.assertTrue(('http://calendarserver.org/ns/', 'push-transports') in resource.liveProperties())
self.assertTrue(('http://calendarserver.org/ns/', 'pushkey') in resource.liveProperties())
+
def test_calendarHomeliveProperties(self):
resource = CalendarHomeResource(None, None, None, StubHome())
self.assertTrue(('http://calendarserver.org/ns/', 'push-transports') in resource.liveProperties())
@@ -70,6 +80,7 @@
self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-heartbeat-uri') in resource.liveProperties())
self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-server') in resource.liveProperties())
+
def test_addressBookHomeliveProperties(self):
resource = AddressBookHomeResource(None, None, None, StubHome())
self.assertTrue(('http://calendarserver.org/ns/', 'push-transports') in resource.liveProperties())
@@ -78,6 +89,7 @@
self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-heartbeat-uri') not in resource.liveProperties())
self.assertTrue(('http://calendarserver.org/ns/', 'xmpp-server') not in resource.liveProperties())
+
@inlineCallbacks
def test_push404(self):
"""
@@ -117,3 +129,215 @@
self.assertEqual((yield resource.readProperty(('http://calendarserver.org/ns/', 'xmpp-uri'), None)), None)
self.assertEqual((yield resource.readProperty(('http://calendarserver.org/ns/', 'xmpp-heartbeat-uri'), None)), None)
self.assertEqual((yield resource.readProperty(('http://calendarserver.org/ns/', 'xmpp-server'), None)), None)
+
+
+
+class OwnershipTests(TestCase):
+ """
+ L{CalDAVResource.isOwner} determines if the authenticated principal of the
+ given request is the owner of that resource.
+ """
+
+ @inlineCallbacks
+ def test_isOwnerUnauthenticated(self):
+ """
+ L{CalDAVResource.isOwner} returns C{False} for unauthenticated requests.
+ """
+ site = None
+ request = SimpleRequest(site, "GET", "/not/a/real/url/")
+ request.authzUser = request.authnUser = Principal(Unauthenticated())
+ rsrc = CalDAVResource()
+ rsrc.owner = lambda igreq: HRef("/somebody/")
+ self.assertEquals((yield rsrc.isOwner(request)), False)
+
+
+ @inlineCallbacks
+ def test_isOwnerNo(self):
+ """
+ L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+ with a principal that matches the resource's owner.
+ """
+ site = None
+ request = SimpleRequest(site, "GET", "/not/a/real/url/")
+ theOwner = Principal(HRef("/yes-i-am-the-owner/"))
+ request.authzUser = request.authnUser = theOwner
+ rsrc = CalDAVResource()
+ rsrc.owner = lambda igreq: HRef("/no-i-am-not-the-owner/")
+ self.assertEquals((yield rsrc.isOwner(request)), False)
+
+
+ @inlineCallbacks
+ def test_isOwnerYes(self):
+ """
+ L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+ with a principal that matches the resource's owner.
+ """
+ site = None
+ request = SimpleRequest(site, "GET", "/not/a/real/url/")
+ theOwner = Principal(HRef("/yes-i-am-the-owner/"))
+ request.authzUser = request.authnUser = theOwner
+ rsrc = CalDAVResource()
+ rsrc.owner = lambda igreq: HRef("/yes-i-am-the-owner/")
+ self.assertEquals((yield rsrc.isOwner(request)), True)
+
+
+ @inlineCallbacks
+ def test_isOwnerAdmin(self):
+ """
+ L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+ with a principal that matches any principal configured in the
+ L{AdminPrincipals} list.
+ """
+ theAdmin = "/read-write-admin/"
+ patchConfig(self, AdminPrincipals=[theAdmin])
+ site = None
+ request = SimpleRequest(site, "GET", "/not/a/real/url/")
+ request.authzUser = request.authnUser = Principal(HRef(theAdmin))
+ rsrc = CalDAVResource()
+ rsrc.owner = lambda igreq: HRef("/some-other-user/")
+ self.assertEquals((yield rsrc.isOwner(request)), True)
+
+
+ @inlineCallbacks
+ def test_isOwnerReadPrincipal(self):
+ """
+ L{CalDAVResource.isOwner} returns C{True} for authenticated requests
+ with a principal that matches any principal configured in the
+ L{AdminPrincipals} list.
+ """
+ theAdmin = "/read-only-admin/"
+ patchConfig(self, ReadPrincipals=[theAdmin])
+ site = None
+ request = SimpleRequest(site, "GET", "/not/a/real/url/")
+ request.authzUser = request.authnUser = Principal(HRef(theAdmin))
+ rsrc = CalDAVResource()
+ rsrc.owner = lambda igreq: HRef("/some-other-user/")
+ self.assertEquals((yield rsrc.isOwner(request)), True)
+
+
+class DefaultAddressBook (TestCase):
+
+ def setUp(self):
+ super(DefaultAddressBook, self).setUp()
+ self.createStockDirectoryService()
+ self.setupCalendars()
+
+ @inlineCallbacks
+ def test_pick_default_addressbook(self):
+ """
+ Make calendar
+ """
+
+
+ request = SimpleRequest(self.site, "GET", "/addressbooks/users/wsanchez/")
+ home = yield request.locateResource("/addressbooks/users/wsanchez")
+
+ # default property initially not present
+ try:
+ home.readDeadProperty(carddavxml.DefaultAddressBookURL)
+ except HTTPError:
+ pass
+ else:
+ self.fail("carddavxml.DefaultAddressBookURL is not empty")
+
+ yield home.pickNewDefaultAddressBook(request)
+
+ try:
+ default = home.readDeadProperty(carddavxml.DefaultAddressBookURL)
+ except HTTPError:
+ self.fail("carddavxml.DefaultAddressBookURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/addressbook")
+
+ request._newStoreTransaction.abort()
+
+ @inlineCallbacks
+ def test_pick_default_other(self):
+ """
+ Make adbk
+ """
+
+
+ request = SimpleRequest(self.site, "GET", "/addressbooks/users/wsanchez/")
+ home = yield request.locateResource("/addressbooks/users/wsanchez")
+
+ # default property not present
+ try:
+ home.readDeadProperty(carddavxml.DefaultAddressBookURL)
+ except HTTPError:
+ pass
+ else:
+ self.fail("carddavxml.DefaultAddressBookURL is not empty")
+
+ # Create a new default adbk
+ newadbk = yield request.locateResource("/addressbooks/users/wsanchez/newadbk")
+ yield newadbk.createAddressBookCollection()
+ home.writeDeadProperty(carddavxml.DefaultAddressBookURL(
+ davxml.HRef("/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newadbk")
+ ))
+ request._newStoreTransaction.commit()
+
+ # Delete the normal adbk
+ request = SimpleRequest(self.site, "GET", "/addressbooks/users/wsanchez/")
+ home = yield request.locateResource("/addressbooks/users/wsanchez")
+ adbk = yield request.locateResource("/addressbooks/users/wsanchez/addressbook")
+ yield adbk.storeRemove(request, False, "/addressbooks/users/wsanchez/addressbook")
+
+ home.removeDeadProperty(carddavxml.DefaultAddressBookURL)
+
+ # default property not present
+ try:
+ home.readDeadProperty(carddavxml.DefaultAddressBookURL)
+ except HTTPError:
+ pass
+ else:
+ self.fail("carddavxml.DefaultAddressBookURL is not empty")
+ request._newStoreTransaction.commit()
+
+ request = SimpleRequest(self.site, "GET", "/addressbooks/users/wsanchez/")
+ home = yield request.locateResource("/addressbooks/users/wsanchez")
+ yield home.pickNewDefaultAddressBook(request)
+
+ try:
+ default = home.readDeadProperty(carddavxml.DefaultAddressBookURL)
+ except HTTPError:
+ self.fail("carddavxml.DefaultAddressBookURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newadbk")
+
+ request._newStoreTransaction.abort()
+
+ @inlineCallbacks
+ def test_fix_shared_default(self):
+ """
+ Make calendar
+ """
+
+
+ request = SimpleRequest(self.site, "GET", "/addressbooks/users/wsanchez/")
+ home = yield request.locateResource("/addressbooks/users/wsanchez")
+
+ # Create a new default adbk
+ newadbk = yield request.locateResource("/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newadbk")
+ yield newadbk.createAddressBookCollection()
+ home.writeDeadProperty(carddavxml.DefaultAddressBookURL(
+ davxml.HRef("/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newadbk")
+ ))
+ try:
+ default = yield home.readProperty(carddavxml.DefaultAddressBookURL, request)
+ except HTTPError:
+ self.fail("carddavxml.DefaultAddressBookURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newadbk")
+
+ # Force the new calendar to think it is a virtual share
+ newadbk._isVirtualShare = True
+
+ try:
+ default = yield home.readProperty(carddavxml.DefaultAddressBookURL, request)
+ except HTTPError:
+ self.fail("carddavxml.DefaultAddressBookURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/addressbooks/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/addressbook")
+
+ request._newStoreTransaction.abort()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_schedule.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_schedule.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_schedule.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,5 +1,5 @@
##
-# Copyright (c) 2005-2007 Apple Inc. All rights reserved.
+# Copyright (c) 2005-2011 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,16 +15,18 @@
##
from twext.web2 import responsecode
-from twext.web2.iweb import IResponse
from twext.web2.dav import davxml
from twext.web2.dav.util import davXMLFromStream
+from twext.web2.http import HTTPError
+from twext.web2.iweb import IResponse
from twext.web2.stream import MemoryStream
from twext.web2.test.test_server import SimpleRequest
+from twisted.internet.defer import inlineCallbacks
+
from twistedcaldav import caldavxml
+from twistedcaldav.test.util import HomeTestCase, TestCase
-from twistedcaldav.test.util import HomeTestCase
-
class Properties (HomeTestCase):
"""
CalDAV properties
@@ -80,3 +82,127 @@
request = SimpleRequest(self.site, "PROPFIND", inbox_uri)
request.stream = MemoryStream(query.toxml())
return self.send(request, propfind_cb)
+
+class DefaultCalendar (TestCase):
+
+ def setUp(self):
+ super(DefaultCalendar, self).setUp()
+ self.createStockDirectoryService()
+ self.setupCalendars()
+
+ @inlineCallbacks
+ def test_pick_default_calendar(self):
+ """
+ Make calendar
+ """
+
+
+ request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property initially not present
+ try:
+ inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
+ except HTTPError:
+ pass
+ else:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
+
+ yield inbox.pickNewDefaultCalendar(request)
+
+ try:
+ default = inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
+ except HTTPError:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ request._newStoreTransaction.abort()
+
+ @inlineCallbacks
+ def test_pick_default_other(self):
+ """
+ Make calendar
+ """
+
+
+ request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # default property not present
+ try:
+ inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
+ except HTTPError:
+ pass
+ else:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
+
+ # Create a new default calendar
+ newcalendar = yield request.locateResource("/calendars/users/wsanchez/newcalendar")
+ yield newcalendar.createCalendarCollection()
+ inbox.writeDeadProperty(caldavxml.ScheduleDefaultCalendarURL(
+ davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+ ))
+
+ # Delete the normal calendar
+ calendar = yield request.locateResource("/calendars/users/wsanchez/calendar")
+ yield calendar.storeRemove(request, False, "/calendars/users/wsanchez/calendar")
+
+ inbox.removeDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
+
+ # default property not present
+ try:
+ inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
+ except HTTPError:
+ pass
+ else:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not empty")
+ request._newStoreTransaction.commit()
+
+ request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+ yield inbox.pickNewDefaultCalendar(request)
+
+ try:
+ default = inbox.readDeadProperty(caldavxml.ScheduleDefaultCalendarURL)
+ except HTTPError:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+
+ request._newStoreTransaction.abort()
+
+ @inlineCallbacks
+ def test_fix_shared_default(self):
+ """
+ Make calendar
+ """
+
+
+ request = SimpleRequest(self.site, "GET", "/calendars/users/wsanchez/")
+ inbox = yield request.locateResource("/calendars/users/wsanchez/inbox")
+
+ # Create a new default calendar
+ newcalendar = yield request.locateResource("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+ yield newcalendar.createCalendarCollection()
+ inbox.writeDeadProperty(caldavxml.ScheduleDefaultCalendarURL(
+ davxml.HRef("/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+ ))
+ try:
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ except HTTPError:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/newcalendar")
+
+ # Force the new calendar to think it is a virtual share
+ newcalendar._isVirtualShare = True
+
+ try:
+ default = yield inbox.readProperty(caldavxml.ScheduleDefaultCalendarURL, request)
+ except HTTPError:
+ self.fail("caldavxml.ScheduleDefaultCalendarURL is not present")
+ else:
+ self.assertEqual(str(default.children[0]), "/calendars/__uids__/6423F94A-6B76-4A3A-815B-D52CFD77935D/calendar")
+
+ request._newStoreTransaction.abort()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_sharing.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_sharing.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -18,16 +18,20 @@
from twext.web2 import responsecode
from twext.web2.dav import davxml
from twext.web2.http_headers import MimeType
+from twext.web2.iweb import IResource
from twext.web2.stream import MemoryStream
from twext.web2.test.test_server import SimpleRequest
from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twistedcaldav import customxml
from twistedcaldav.config import config
from twistedcaldav.test.util import HomeTestCase, norequest
-from twistedcaldav.memcacher import Memcacher
+from twistedcaldav.sharing import SharedCollectionMixin, SHARETYPE_DIRECT, WikiDirectoryService
+
from twistedcaldav.resource import CalDAVResource
from txdav.common.datastore.test.util import buildStore, StubNotifierFactory
+from zope.interface import implements
+
sharedOwnerType = davxml.ResourceType.sharedownercalendar #@UndefinedVariable
regularCalendarType = davxml.ResourceType.calendar #@UndefinedVariable
@@ -542,14 +546,89 @@
self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite())
+ @inlineCallbacks
+ def test_wikiACL(self):
+ """
+ Ensure shareeAccessControlList( ) honors the access granted by the wiki
+ to the sharee, so that delegates of the sharee get the same level of
+ access.
+ """
+ def stubWikiAccessMethod(userID, wikiID):
+ return access
+
+ class StubCollection(object):
+ def __init__(self):
+ self._isVirtualShare = True
+ self._shareePrincipal = StubUserPrincipal()
+ def isCalendarCollection(self):
+ return True
+
+ class StubShare(object):
+ def __init__(self):
+ self.sharetype = SHARETYPE_DIRECT
+ self.hosturl = "/wikifoo"
+
+ class TestCollection(SharedCollectionMixin, StubCollection):
+ pass
+
+ class StubRecord(object):
+ def __init__(self, recordType, name, guid):
+ self.recordType = recordType
+ self.shortNames = [name]
+ self.guid = guid
+
+ class StubUserPrincipal(object):
+ def __init__(self):
+ self.record = StubRecord(
+ "users",
+ "testuser",
+ "4F364813-0415-45CB-9FD4-DBFEF7A0A8E0"
+ )
+ def principalURL(self):
+ return "/principals/__uids__/%s/" % (self.record.guid,)
+
+ class StubWikiPrincipal(object):
+ def __init__(self):
+ self.record = StubRecord(
+ WikiDirectoryService.recordType_wikis,
+ "wikifoo",
+ "foo"
+ )
+
+ class StubWikiResource(object):
+ implements(IResource)
+
+ def locateChild(self, req, segments):
+ pass
+ def renderHTTP(req):
+ pass
+ def ownerPrincipal(self, req):
+ return succeed(StubWikiPrincipal())
+
+
+ collection = TestCollection()
+ collection._share = StubShare()
+ self.site.resource.putChild("wikifoo", StubWikiResource())
+ request = SimpleRequest(self.site, "GET", "/wikifoo")
+
+ # Simulate the wiki server granting Read access
+ access = "read"
+ acl = (yield collection.shareeAccessControlList(request,
+ wikiAccessMethod=stubWikiAccessMethod))
+ self.assertFalse("<write/>" in acl.toxml())
+
+ # Simulate the wiki server granting Read-Write access
+ access = "write"
+ acl = (yield collection.shareeAccessControlList(request,
+ wikiAccessMethod=stubWikiAccessMethod))
+ self.assertTrue("<write/>" in acl.toxml())
+
+
class DatabaseSharingTests(SharingTests):
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(Memcacher, "allowTestCache", True)
self.calendarStore = yield buildStore(self, StubNotifierFactory())
yield super(DatabaseSharingTests, self).setUp()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_timezonestdservice.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_timezonestdservice.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_timezonestdservice.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -30,7 +30,7 @@
def test_generateXML(self):
hashed = hashlib.md5("test").hexdigest()
- info = TimezoneInfo("America/New_York", "20110517T120000Z", hashed)
+ info = TimezoneInfo("America/New_York", ("US/Eastern",), "20110517T120000Z", hashed)
node = Element("root")
info.generateXML(node)
@@ -39,12 +39,13 @@
self.assertTrue(timezone is not None)
self.assertEqual(timezone.findtext("tzid"), "America/New_York")
self.assertEqual(timezone.findtext("dtstamp"), "20110517T120000Z")
+ self.assertEqual(timezone.findtext("alias"), "US/Eastern")
self.assertEqual(timezone.findtext("md5"), hashed)
def test_parseXML(self):
hashed = hashlib.md5("test").hexdigest()
- info1 = TimezoneInfo("America/New_York", "20110517T120000Z", hashed)
+ info1 = TimezoneInfo("America/New_York", ("US/Eastern",), "20110517T120000Z", hashed)
node = Element("root")
info1.generateXML(node)
@@ -53,6 +54,7 @@
info2 = TimezoneInfo.readXML(timezone)
self.assertEqual(info2.tzid, "America/New_York")
+ self.assertEqual(info2.aliases, ("US/Eastern",))
self.assertEqual(info2.dtstamp, "20110517T120000Z")
self.assertEqual(info2.md5, hashed)
@@ -98,36 +100,50 @@
self.assertEqual(db1.dtstamp, db2.dtstamp)
self.assertEqual(len(db1.timezones), len(db2.timezones))
- def testGetEmpty(self):
+ def testList(self):
xmlfile = self.mktemp()
db = PrimaryTimezoneDatabase(TimezoneCache.getDBPath(), xmlfile)
db.createNewDatabase()
self.assertTrue(os.path.exists(xmlfile))
- tz1 = db.getTimezones(())
- self.assertTrue(str(tz1).find("VTIMEZONE") == -1)
+ tzids = set([tz.tzid for tz in db.listTimezones(None)])
+ self.assertTrue("America/New_York" in tzids)
+ self.assertTrue("US/Eastern" not in tzids)
- def testGetOne(self):
+ def testListChangedSince(self):
xmlfile = self.mktemp()
db = PrimaryTimezoneDatabase(TimezoneCache.getDBPath(), xmlfile)
db.createNewDatabase()
self.assertTrue(os.path.exists(xmlfile))
- tz1 = db.getTimezones(("America/New_York",))
- self.assertTrue(str(tz1).find("VTIMEZONE") != -1)
- self.assertTrue(str(tz1).find("TZID:America/New_York") != -1)
+ tzids = set([tz.tzid for tz in db.listTimezones(db.dtstamp)])
+ self.assertTrue(len(tzids) == 0)
- def testGetMultiple(self):
+ def testGetNone(self):
xmlfile = self.mktemp()
db = PrimaryTimezoneDatabase(TimezoneCache.getDBPath(), xmlfile)
db.createNewDatabase()
self.assertTrue(os.path.exists(xmlfile))
- tz1 = db.getTimezones(("America/New_York","America/Los_Angeles","America/Bogus",))
+ tz = db.getTimezone("Bogus")
+ self.assertEqual(tz, None)
+
+ def testGetOne(self):
+
+ xmlfile = self.mktemp()
+ db = PrimaryTimezoneDatabase(TimezoneCache.getDBPath(), xmlfile)
+ db.createNewDatabase()
+ self.assertTrue(os.path.exists(xmlfile))
+
+ # Original
+ tz1 = db.getTimezone("America/New_York")
self.assertTrue(str(tz1).find("VTIMEZONE") != -1)
self.assertTrue(str(tz1).find("TZID:America/New_York") != -1)
- self.assertTrue(str(tz1).find("TZID:America/Los_Angeles") != -1)
- self.assertTrue(str(tz1).find("TZID:America/Bogus") == -1)
+
+ # Alias
+ tz1 = db.getTimezone("US/Eastern")
+ self.assertTrue(str(tz1).find("VTIMEZONE") != -1)
+ self.assertTrue(str(tz1).find("TZID:US/Eastern") != -1)
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_validation.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_validation.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_validation.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -48,8 +48,10 @@
def _getSampleCalendar(self):
return Component.fromString("""BEGIN:VCALENDAR
VERSION:2.0
+PRODID:-//Apple Computer\, Inc//iCal 2.0//EN
BEGIN:VEVENT
UID:12345-67890
+DTSTAMP:20071114T000000Z
DTSTART:20071114T000000Z
ORGANIZER:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_wrapping.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_wrapping.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_wrapping.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -46,7 +46,12 @@
StubNotifierFactory
-from twistedcaldav.memcacher import Memcacher
+from twext.web2.http import HTTPError
+from twext.web2.responsecode import INSUFFICIENT_STORAGE_SPACE
+from twext.web2.stream import MemoryStream
+from txdav.common.datastore.test.util import deriveQuota
+from twistedcaldav.test.util import patchConfig
+from twistedcaldav.directory.test.test_xmlfile import XMLFileBase
from txdav.caldav.icalendarstore import ICalendarHome
from txdav.carddav.iaddressbookstore import IAddressBookHome
@@ -73,8 +78,10 @@
def finish(self):
pass
+ remoteAddr = '127.0.0.1'
+
class WrappingTests(TestCase):
"""
Tests for L{twistedcaldav.static.CalDAVResource} creating the appropriate type
@@ -154,7 +161,7 @@
requestUnderTest = None
@inlineCallbacks
- def getResource(self, path):
+ def getResource(self, path, method='GET', user=None):
"""
Retrieve a resource from the site.
@@ -162,15 +169,31 @@
slash)
@type path: C{str}
+
+ @param method: the HTTP method to initialize the request with.
+ Defaults to GET. (This should I{mostly} be irrelevant to path
+ traversal, but may be interesting to subsequent operations on
+ C{self.requestUnderTest}).
+
+ @param user: the username (shortname in the test XML file) of the user
+ to forcibly authenticate this request as.
+
+ @return: a L{Deferred} that fires with an L{IResource}.
"""
if self.requestUnderTest is None:
- req = self.requestForPath(path)
+ req = self.requestForPath(path, method)
self.requestUnderTest = req
else:
+ # How should this handle mismatched methods?
req = self.requestUnderTest
aResource = yield req.locateResource(
"http://localhost:8008/" + path
)
+ if user is not None:
+ guid = XMLFileBase.users[user]["guid"]
+ req.authnUser = req.authzUser = (
+ davxml.Principal(davxml.HRef('/principals/__uids__/' + guid + '/'))
+ )
returnValue(aResource)
@@ -183,9 +206,9 @@
return self.requestUnderTest._newStoreTransaction.commit()
- def requestForPath(self, path):
+ def requestForPath(self, path, method='GET'):
"""
- Get a L{Request} with a L{FakeChanRequest} for a given path.
+ Get a L{Request} with a L{FakeChanRequest} for a given path and method.
"""
headers = Headers()
headers.addRawHeader("Host", "localhost:8008")
@@ -193,13 +216,17 @@
req = Request(
site=self.site,
chanRequest=chanReq,
- command='GET',
+ command=method,
path=path,
version=('1', '1'),
contentLength=0,
headers=headers
)
- req.path = path # normally process( ) sets request.path
+
+ # 'process()' normally sets these. Shame on web2, having so much
+ # partially-initialized stuff floating around.
+ req.remoteAddr = '127.0.0.1'
+ req.path = path
req.credentialFactories = {}
return req
@@ -368,6 +395,28 @@
@inlineCallbacks
+ def test_attachmentQuotaExceeded(self):
+ """
+ Exceeding quota on an attachment returns an HTTP error code.
+ """
+ patchConfig(testCase=self, EnableDropBox=True)
+ yield self.populateOneObject("1.ics", event4_text)
+ calendarObject = yield self.getResource(
+ "/calendars/users/wsanchez/dropbox/uid4.dropbox/too-big-attachment",
+ "PUT", "wsanchez"
+ )
+ self.requestUnderTest.stream = MemoryStream(
+ "x" * deriveQuota(self.id()) * 2)
+ try:
+ result = yield calendarObject.http_PUT(self.requestUnderTest)
+ except HTTPError, he:
+ self.assertEquals(he.response.code, INSUFFICIENT_STORAGE_SPACE)
+ else:
+ self.fail("Error not raised, %r returned instead." %
+ (result,))
+
+
+ @inlineCallbacks
def test_lookupNewCalendarObject(self):
"""
When a L{CalDAVResource} representing a new calendar object on a
@@ -470,9 +519,6 @@
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(Memcacher, "allowTestCache", True)
self.calendarStore = yield buildStore(self, StubNotifierFactory())
super(DatabaseWrappingTests, self).setUp()
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_xml.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/test_xml.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -30,7 +30,8 @@
calendar_file = os.path.join(os.path.dirname(__file__), "data", "Holidays",
"C3184A66-1ED0-11D9-A5E0-000A958A3252.ics")
calendar = Component.fromStream(file(calendar_file))
- calendar.validateForCalDAV()
+ calendar.validCalendarData()
+ calendar.validCalendarForCalDAV(methodAllowed=False)
def test_ComponentFilter(self):
"""
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/test/util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -43,6 +43,7 @@
DirectoryPrincipalProvisioningResource)
from twistedcaldav.directory.xmlfile import XMLDirectoryService
+from txdav.common.datastore.test.util import deriveQuota
from txdav.common.datastore.file import CommonDataStore
@@ -80,15 +81,13 @@
self.xmlFile = FilePath(config.DataRoot).child("accounts.xml")
self.xmlFile.setContent(xmlFile.getContent())
- # *temporarily* set up an augment service so this directory service will
- # work.
- self.patch(augment, "AugmentService", augment.AugmentXMLDB(
- xmlFiles=(augmentsFile.path,)
- )
- )
self.directoryService = XMLDirectoryService(
- {'xmlFile' : "accounts.xml"}
+ {
+ "xmlFile" : "accounts.xml",
+ "augmentService" :
+ augment.AugmentXMLDB( xmlFiles=(augmentsFile.path,)),
+ }
)
# FIXME: see FIXME in DirectoryPrincipalProvisioningResource.__init__;
@@ -105,13 +104,15 @@
addressbooks.) By default returns a L{CommonDataStore}, but this is a
hook for subclasses to override to provide different data stores.
"""
- return CommonDataStore(FilePath(config.DocumentRoot), None, True, False)
+ return CommonDataStore(FilePath(config.DocumentRoot), None, True, False,
+ quota=deriveQuota(self.id()))
def setupCalendars(self):
"""
- Set up the resource at /calendars (a L{DirectoryCalendarHomeProvisioningResource}),
- and assign it as C{self.calendarCollection}.
+ Set up the resource at /calendars (a
+ L{DirectoryCalendarHomeProvisioningResource}), and assign it as
+ C{self.calendarCollection}.
"""
# Need a data store
@@ -586,8 +587,21 @@
+def patchConfig(testCase, **kw):
+ """
+ Patch the global configuration (including running the appropriate hooks) for
+ the duration of the given test.
+ """
+ preserved = {}
+ for k in kw:
+ preserved[k] = config.get(k, None)
+ def reUpdate():
+ config.update(preserved)
+ testCase.addCleanup(reUpdate)
+ config.update(kw)
+
class ErrorOutput(Exception):
"""
The process produced some error output and exited with a non-zero exit
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/upgrade.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/upgrade.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -928,8 +928,7 @@
owner = LocalCalendarUser(cua, ownerPrincipal,
inbox, ownerPrincipal.scheduleInboxURL())
- data = yield inboxItem.iCalendarText()
- calendar = Component.fromString(data)
+ calendar = yield inboxItem.iCalendar()
try:
method = calendar.propertyValue("METHOD")
except ValueError:
Modified: CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/vcard.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/vcard.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/twistedcaldav/vcard.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -27,15 +27,18 @@
import cStringIO as StringIO
import codecs
+from twext.python.log import Logger
from twext.web2.stream import IStream
from twext.web2.dav.util import allDataFromStream
from pycalendar.attribute import PyCalendarAttribute
from pycalendar.componentbase import PyCalendarComponentBase
-from pycalendar.exceptions import PyCalendarInvalidData
+from pycalendar.exceptions import PyCalendarError
from pycalendar.vcard.card import Card
from pycalendar.vcard.property import Property as pyProperty
+log = Logger()
+
vCardProductID = "-//CALENDARSERVER.ORG//NONSGML Version 1//EN"
class InvalidVCardDataError(ValueError):
@@ -197,7 +200,7 @@
"""
try:
results = Card.parseMultiple(stream)
- except PyCalendarInvalidData:
+ except PyCalendarError:
results = None
if not results:
stream.seek(0)
@@ -235,7 +238,7 @@
cal = Card()
try:
result = cal.parse(stream)
- except PyCalendarInvalidData:
+ except PyCalendarError:
result = None
if not result:
stream.seek(0)
@@ -409,6 +412,26 @@
return self._resource_uid
+ def validVCardData(self, doFix=True, doRaise=True):
+ """
+ @return: tuple of fixed, unfixed issues
+ @raise InvalidVCardDataError: if the given vcard data is not valid.
+ """
+ if self.name() != "VCARD":
+ log.debug("Not a vcard: %s" % (self,))
+ raise InvalidVCardDataError("Not a vcard")
+
+ # Do underlying vCard library validation with data fix
+ fixed, unfixed = self._pycard.validate(doFix=doFix)
+ if unfixed:
+ log.debug("vCard data had unfixable problems:\n %s" % ("\n ".join(unfixed),))
+ if doRaise:
+ raise InvalidVCardDataError("Calendar data had unfixable problems:\n %s" % ("\n ".join(unfixed),))
+ if fixed:
+ log.debug("vCard data had fixable problems:\n %s" % ("\n ".join(fixed),))
+
+ return fixed, unfixed
+
def validForCardDAV(self):
"""
@raise ValueError: if the given vcard data is not valid.
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/base
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/base/datastore
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/base/datastore/subpostgres.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/base/datastore/subpostgres.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/base/datastore/subpostgres.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -87,8 +87,9 @@
def processEnded(self, reason):
log.msg("postgres process ended %r" % (reason,))
+ result = (reason.value.status == 0)
self.lineReceiver.connectionLost(reason)
- self.completionDeferred.callback(None)
+ self.completionDeferred.callback(result)
@@ -362,6 +363,7 @@
)
self.monitor = monitor
def gotReady(result):
+ self.shouldStopDatabase = result
self.ready()
self.deactivateDelayedShutdown()
def reportit(f):
@@ -370,6 +372,7 @@
self.monitor.completionDeferred.addCallback(
gotReady).addErrback(reportit)
+ shouldStopDatabase = False
def startService(self):
MultiService.startService(self)
@@ -423,14 +426,17 @@
d = MultiService.stopService(self)
def superStopped(result):
- monitor = _PostgresMonitor()
- pg_ctl = which("pg_ctl")[0]
- reactor.spawnProcess(monitor, pg_ctl,
- [pg_ctl, '-l', 'logfile', 'stop'],
- self.env,
- uid=self.uid, gid=self.gid,
- )
- return monitor.completionDeferred
+ # If pg_ctl's startup wasn't successful, don't bother to stop the
+ # database. (This also happens in command-line tools.)
+ if self.shouldStopDatabase:
+ monitor = _PostgresMonitor()
+ pg_ctl = which("pg_ctl")[0]
+ reactor.spawnProcess(monitor, pg_ctl,
+ [pg_ctl, '-l', 'logfile', 'stop'],
+ self.env,
+ uid=self.uid, gid=self.gid,
+ )
+ return monitor.completionDeferred
return d.addCallback(superStopped)
# def maybeStopSubprocess(result):
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/base/propertystore
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/base/propertystore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/base/propertystore/test/test_sql.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/base/propertystore/test/test_sql.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -26,10 +26,8 @@
from txdav.base.propertystore.test.base import (
PropertyStoreTest, propertyName, propertyValue)
-from twistedcaldav import memcacher
from twisted.internet.defer import gatherResults
from twext.enterprise.ienterprise import AlreadyFinishedError
-from twistedcaldav.config import config
try:
from txdav.base.propertystore.sql import PropertyStore
@@ -45,10 +43,6 @@
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(memcacher.Memcacher, "allowTestCache", True)
-
self.notifierFactory = StubNotifierFactory()
self.store = yield buildStore(self, self.notifierFactory)
self.addCleanup(self.maybeCommitLast)
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+from twistedcaldav.ical import InvalidICalendarDataError
"""
File calendar store.
@@ -31,13 +32,12 @@
from errno import ENOENT
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.internet.interfaces import ITransport
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed, fail
+
from twisted.python.failure import Failure
from txdav.base.propertystore.xattr import PropertyStore
-from twext.python.vcomponent import InvalidICalendarDataError
from twext.python.vcomponent import VComponent
from twext.web2.dav import davxml
from twext.web2.dav.element.rfc2518 import ResourceType, GETContentType
@@ -55,13 +55,14 @@
from txdav.caldav.datastore.index_file import Index as OldIndex,\
IndexSchedule as OldInboxIndex
from txdav.caldav.datastore.util import (
- validateCalendarComponent, dropboxIDFromCalendarObject
+ validateCalendarComponent, dropboxIDFromCalendarObject, CalendarObjectBase,
+ StorageTransportBase
)
from txdav.common.datastore.file import (
CommonDataStore, CommonStoreTransaction, CommonHome, CommonHomeChild,
- CommonObjectResource
-, CommonStubResource)
+ CommonObjectResource, CommonStubResource)
+from txdav.caldav.icalendarstore import QuotaExceeded
from txdav.common.icommondatastore import (NoSuchObjectResourceError,
InternalDataStoreError)
@@ -86,7 +87,8 @@
_notifierPrefix = "CalDAV"
def __init__(self, uid, path, calendarStore, transaction, notifiers):
- super(CalendarHome, self).__init__(uid, path, calendarStore, transaction, notifiers)
+ super(CalendarHome, self).__init__(uid, path, calendarStore,
+ transaction, notifiers)
self._childClass = Calendar
@@ -262,7 +264,7 @@
-class CalendarObject(CommonObjectResource):
+class CalendarObject(CommonObjectResource, CalendarObjectBase):
"""
@ivar _path: The path of the .ics file on disk
@@ -300,8 +302,8 @@
self.name(), component
)
- self._component = component
- # FIXME: needs to clear text cache
+ componentText = str(component)
+ self._objectText = componentText
def do():
# Mark all properties as dirty, so they can be added back
@@ -313,7 +315,6 @@
backup = hidden(self._path.temporarySibling())
self._path.moveTo(backup)
- componentText = str(component)
fh = self._path.open("w")
try:
# FIXME: concurrency problem; if this write is interrupted
@@ -340,25 +341,38 @@
def component(self):
- if self._component is not None:
- return self._component
- text = self.text()
-
+ """
+ Read calendar data and validate/fix it. Do not raise a store error here if there are unfixable
+ errors as that could prevent the overall request to fail. Instead we will hand bad data off to
+ the caller - that is not ideal but in theory we should have checked everything on the way in and
+ only allowed in good data.
+ """
+ text = self._text()
try:
component = VComponent.fromString(text)
- # Fix any bogus data we can
- component.validateComponentsForCalDAV(False, fix=True)
except InvalidICalendarDataError, e:
+ # This is a really bad situation, so do raise
raise InternalDataStoreError(
"File corruption detected (%s) in file: %s"
% (e, self._path.path)
)
+
+ # Fix any bogus data we can
+ fixed, unfixed = component.validCalendarData(doFix=True, doRaise=False)
+
+ if unfixed:
+ self.log_error("Calendar data at %s had unfixable problems:\n %s" % (self._path.path, "\n ".join(unfixed),))
+
+ if fixed:
+ self.log_error("Calendar data at %s had fixable problems:\n %s" % (self._path.path, "\n ".join(fixed),))
+
return component
- def text(self):
- if self._component is not None:
- return str(self._component)
+ def _text(self):
+ if self._objectText is not None:
+ return self._objectText
+
try:
fh = self._path.open()
except IOError, e:
@@ -388,10 +402,10 @@
"File corruption detected (improper start) in file: %s"
% (self._path.path,)
)
+
+ self._objectText = text
return text
- iCalendarText = text
-
def uid(self):
if not hasattr(self, "_uid"):
self._uid = self.component().resourceUID()
@@ -497,14 +511,14 @@
# FIXME: rollback, tests for rollback
attachment = (yield self.attachmentWithName(name))
- old_size = attachment.size()
+ oldSize = attachment.size()
(yield self._dropboxPath()).child(name).remove()
if name in self._attachments:
del self._attachments[name]
# Adjust quota
- self._calendar._home.adjustQuotaUsedBytes(-old_size)
+ self._calendar._home.adjustQuotaUsedBytes(-oldSize)
@inlineCallbacks
@@ -580,10 +594,8 @@
-class AttachmentStorageTransport(object):
+class AttachmentStorageTransport(StorageTransportBase):
- implements(ITransport)
-
def __init__(self, attachment, contentType):
"""
Initialize this L{AttachmentStorageTransport} and open its file for
@@ -592,9 +604,10 @@
@param attachment: The attachment whose data is being filled out.
@type attachment: L{Attachment}
"""
- self._attachment = attachment
- self._contentType = contentType
- self._file = self._attachment._path.open("w")
+ super(AttachmentStorageTransport, self).__init__(
+ attachment, contentType)
+ self._path = self._attachment._path.temporarySibling()
+ self._file = self._path.open("w")
def write(self, data):
@@ -603,20 +616,30 @@
def loseConnection(self):
-
- old_size = self._attachment.size()
-
+ home = self._attachment._calendarObject._calendar._home
+ oldSize = self._attachment.size()
+ newSize = self._file.tell()
# FIXME: do anything
self._file.close()
+ if home.quotaAllowedBytes() < (home.quotaUsedBytes()
+ + (newSize - oldSize)):
+ self._path.remove()
+ return fail(QuotaExceeded())
+
+ self._path.moveTo(self._attachment._path)
+
md5 = hashlib.md5(self._attachment._path.getContent()).hexdigest()
props = self._attachment.properties()
- props[contentTypeKey] = GETContentType(generateContentType(self._contentType))
+ props[contentTypeKey] = GETContentType(
+ generateContentType(self._contentType)
+ )
props[md5key] = TwistedGETContentMD5.fromString(md5)
# Adjust quota
- self._attachment._calendarObject._calendar._home.adjustQuotaUsedBytes(self._attachment.size() - old_size)
+ home.adjustQuotaUsedBytes(newSize - oldSize)
props.flush()
+ return succeed(None)
@@ -644,7 +667,7 @@
def properties(self):
uid = self._calendarObject._parentCollection._home.uid()
- return PropertyStore(uid, lambda :self._path)
+ return PropertyStore(uid, lambda: self._path)
def store(self, contentType):
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1046,7 +1046,8 @@
# FIXME: This is blocking I/O
try:
calendar = Component.fromStream(stream)
- calendar.validateForCalDAV()
+ calendar.validCalendarData()
+ calendar.validCalendarForCalDAV(methodAllowed=False)
except ValueError:
log.err("Non-calendar resource: %s" % (name,))
else:
@@ -1149,8 +1150,8 @@
# FIXME: This is blocking I/O
try:
calendar = Component.fromStream(stream)
- calendar.validCalendarForCalDAV()
- calendar.validateComponentsForCalDAV(True)
+ calendar.validCalendarData()
+ calendar.validCalendarForCalDAV(methodAllowed=True)
except ValueError:
log.err("Non-calendar resource: %s" % (name,))
else:
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
+ /CalendarServer/branches/config-separation/txdav/caldav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/caldav/datastore/index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/index.py:6322-6394
/CalendarServer/trunk/txdav/caldav/datastore/index_file.py:7443-7695
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/scheduling.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/scheduling.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/scheduling.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,6 +1,6 @@
# -*- test-case-name: txdav.caldav.datastore.test.test_scheduling -*-
##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 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.
@@ -119,7 +119,6 @@
implements(ICalendarObject)
def setComponent(self, component): ""
def component(self): ""
- def iCalendarText(self): ""
def uid(self): ""
def componentType(self): ""
def organizer(self): ""
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/sql.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/sql.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -31,7 +31,6 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.error import ConnectionLost
-from twisted.internet.interfaces import ITransport
from twisted.python import hashlib
from twisted.python.failure import Failure
@@ -40,7 +39,7 @@
from twistedcaldav.config import config
from twistedcaldav.dateops import normalizeForIndex, datetimeMktime,\
parseSQLTimestamp, pyCalendarTodatetime
-from twistedcaldav.ical import Component
+from twistedcaldav.ical import Component, InvalidICalendarDataError
from twistedcaldav.instance import InvalidOverriddenInstanceError
from twistedcaldav.memcacher import Memcacher
@@ -67,8 +66,14 @@
from twext.enterprise.dal.syntax import Parameter
from twext.enterprise.dal.syntax import utcNowSQL
from twext.enterprise.dal.syntax import Len
-from txdav.common.icommondatastore import IndexedSearchException
+from txdav.caldav.datastore.util import CalendarObjectBase
+from txdav.caldav.icalendarstore import QuotaExceeded
+
+from txdav.caldav.datastore.util import StorageTransportBase
+from txdav.common.icommondatastore import IndexedSearchException,\
+ InternalDataStoreError
+
from pycalendar.datetime import PyCalendarDateTime
from pycalendar.duration import PyCalendarDuration
from pycalendar.timezone import PyCalendarTimezone
@@ -103,6 +108,7 @@
super(CalendarHome, self).__init__(transaction, ownerUID, notifiers)
self._shares = SQLLegacyCalendarShares(self)
+
createCalendarWithName = CommonHome.createChildWithName
removeCalendarWithName = CommonHome.removeChildWithName
calendarWithName = CommonHome.childWithName
@@ -113,7 +119,7 @@
@inlineCallbacks
def hasCalendarResourceUIDSomewhereElse(self, uid, ok_object, type):
-
+
objectResources = (yield self.objectResourcesWithUID(uid, ("inbox",)))
for objectResource in objectResources:
if ok_object and objectResource._resourceID == ok_object._resourceID:
@@ -121,18 +127,18 @@
matched_type = "schedule" if objectResource.isScheduleObject else "calendar"
if type == "schedule" or matched_type == "schedule":
returnValue(True)
-
+
returnValue(False)
@inlineCallbacks
def getCalendarResourcesForUID(self, uid, allow_shared=False):
-
+
results = []
objectResources = (yield self.objectResourcesWithUID(uid, ("inbox",)))
for objectResource in objectResources:
if allow_shared or objectResource._parentCollection._owned:
results.append(objectResource)
-
+
returnValue(results)
@@ -305,7 +311,7 @@
-class CalendarObject(CommonObjectResource):
+class CalendarObject(CommonObjectResource, CalendarObjectBase):
implements(ICalendarObject)
_objectTable = CALENDAR_OBJECT_TABLE
@@ -314,7 +320,7 @@
def __init__(self, calendar, name, uid, resourceID=None, metadata=None):
super(CalendarObject, self).__init__(calendar, name, uid, resourceID)
-
+
if metadata is None:
metadata = {}
self.accessMode = metadata.get("accessMode", "")
@@ -406,7 +412,7 @@
expand = PyCalendarDateTime(2100, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
doInstanceIndexing = True
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:
doInstanceIndexing = True
@@ -453,11 +459,11 @@
else:
raise
- # Now coerce indexing to off if needed
+ # Now coerce indexing to off if needed
if not doInstanceIndexing:
instances = None
recurrenceLimit = PyCalendarDateTime(1900, 1, 1, 0, 0, 0, tzid=PyCalendarTimezone(utc=True))
-
+
co = schema.CALENDAR_OBJECT
tr = schema.TIME_RANGE
tpy = schema.TRANSPARENCY
@@ -470,13 +476,13 @@
organizer = component.getOrganizer()
if not organizer:
organizer = ""
-
+
# CALENDAR_OBJECT table update
self._uid = component.resourceUID()
self._md5 = hashlib.md5(componentText).hexdigest()
self._size = len(componentText)
- # Special - if migrating we need to preserve the original md5
+ # Special - if migrating we need to preserve the original md5
if self._txn._migrating and hasattr(component, "md5"):
self._md5 = component.md5
@@ -494,7 +500,7 @@
# server dropbox collections and only then set the read mode
self._attachment = _ATTACHMENTS_MODE_READ
self._dropboxID = (yield self.dropboxID())
-
+
values = {
co.CALENDAR_RESOURCE_ID : self._calendar._resourceID,
co.RESOURCE_NAME : self._name,
@@ -513,7 +519,7 @@
co.PRIVATE_COMMENTS : self._private_comments,
co.MD5 : self._md5
}
-
+
if inserting:
self._resourceID, self._created, self._modified = (
yield Insert(
@@ -539,12 +545,12 @@
co.RECURRANCE_MAX :
pyCalendarTodatetime(normalizeForIndex(recurrenceLimit)) if recurrenceLimit else None,
}
-
+
yield Update(
values,
Where=co.RESOURCE_ID == self._resourceID
).on(self._txn)
-
+
# Need to wipe the existing time-range for this and rebuild
yield Delete(
From=tr,
@@ -580,7 +586,7 @@
tpy.USER_ID : useruid,
tpy.TRANSPARENT : transp,
}).on(self._txn))
-
+
# Special - for unbounded recurrence we insert a value for "infinity"
# that will allow an open-ended time-range to always match it.
if component.isRecurringUnbounded():
@@ -609,12 +615,35 @@
@inlineCallbacks
def component(self):
- returnValue(VComponent.fromString((yield self.iCalendarText())))
+ """
+ Read calendar data and validate/fix it. Do not raise a store error here if there are unfixable
+ errors as that could prevent the overall request to fail. Instead we will hand bad data off to
+ the caller - that is not ideal but in theory we should have checked everything on the way in and
+ only allowed in good data.
+ """
+ text = yield self._text()
+ try:
+ component = VComponent.fromString(text)
+ except InvalidICalendarDataError, e:
+ # This is a really bad situation, so do raise
+ raise InternalDataStoreError(
+ "Data corruption detected (%s) in id: %s"
+ % (e, self._resourceID)
+ )
- iCalendarText = CommonObjectResource.text
+ # Fix any bogus data we can
+ fixed, unfixed = component.validCalendarData(doFix=True, doRaise=False)
+ if unfixed:
+ self.log_error("Calendar data id=%s had unfixable problems:\n %s" % (self._resourceID, "\n ".join(unfixed),))
+
+ if fixed:
+ self.log_error("Calendar data id=%s had fixable problems:\n %s" % (self._resourceID, "\n ".join(fixed),))
+ returnValue(component)
+
+
@inlineCallbacks
def organizer(self):
returnValue((yield self.component()).getOrganizer())
@@ -622,7 +651,7 @@
def getMetadata(self):
metadata = {}
- metadata["accessMode"] = self.accessMode
+ metadata["accessMode"] = self.accessMode
metadata["isScheduleObject"] = self.isScheduleObject
metadata["scheduleTag"] = self.scheduleTag
metadata["scheduleEtags"] = self.scheduleEtags
@@ -671,7 +700,7 @@
@inlineCallbacks
def createAttachmentWithName(self, name):
-
+
# We need to know the resource_ID of the home collection of the owner (not sharee)
# of this event
sharerHomeID = (yield self._parentCollection.sharerHomeID())
@@ -683,7 +712,7 @@
yield attachment.remove()
def attachmentWithName(self, name):
- return Attachment.attachmentWithName(self._txn, self._dropboxID, name)
+ return Attachment.loadWithName(self._txn, self._dropboxID, name)
def attendeesCanManageAttachments(self):
return self._attachment == _ATTACHMENTS_MODE_WRITE
@@ -731,56 +760,73 @@
"""
return MimeType.fromString("text/calendar; charset=utf-8")
-class AttachmentStorageTransport(object):
- implements(ITransport)
- def __init__(self, attachment, contentType):
- self.attachment = attachment
- self.contentType = contentType
- self.buf = ''
- self.hash = hashlib.md5()
+class AttachmentStorageTransport(StorageTransportBase):
+ def __init__(self, attachment, contentType, creating=False):
+ super(AttachmentStorageTransport, self).__init__(
+ attachment, contentType)
+ self._buf = ''
+ self._hash = hashlib.md5()
+ self._creating = creating
+
@property
def _txn(self):
- return self.attachment._txn
+ return self._attachment._txn
def write(self, data):
- self.buf += data
- self.hash.update(data)
+ if isinstance(data, buffer):
+ data = str(data)
+ self._buf += data
+ self._hash.update(data)
@inlineCallbacks
def loseConnection(self):
- old_size = self.attachment.size()
+ # FIXME: this should be synchronously accessible; IAttachment should
+ # have a method for getting its parent just as CalendarObject/Calendar
+ # do.
- self.attachment._path.setContent(self.buf)
- self.attachment._contentType = self.contentType
- self.attachment._md5 = self.hash.hexdigest()
- self.attachment._size = len(self.buf)
+ # FIXME: If this method isn't called, the transaction should be
+ # prevented from committing successfully. It's not valid to have an
+ # attachment that doesn't point to a real file.
+
+ home = (yield self._txn.calendarHomeWithResourceID(
+ self._attachment._ownerHomeID))
+
+ oldSize = self._attachment.size()
+
+ if home.quotaAllowedBytes() < ((yield home.quotaUsedBytes())
+ + (len(self._buf) - oldSize)):
+ if self._creating:
+ yield self._attachment._internalRemove()
+ raise QuotaExceeded()
+
+ self._attachment._path.setContent(self._buf)
+ self._attachment._contentType = self._contentType
+ self._attachment._md5 = self._hash.hexdigest()
+ self._attachment._size = len(self._buf)
att = schema.ATTACHMENT
- self.attachment._created, self.attachment._modified = map(
+ self._attachment._created, self._attachment._modified = map(
sqltime,
(yield Update(
{
- att.CONTENT_TYPE : generateContentType(self.contentType),
- att.SIZE : self.attachment._size,
- att.MD5 : self.attachment._md5,
+ att.CONTENT_TYPE : generateContentType(self._contentType),
+ att.SIZE : self._attachment._size,
+ att.MD5 : self._attachment._md5,
att.MODIFIED : utcNowSQL
},
- Where=att.PATH == self.attachment.name(),
+ Where=att.PATH == self._attachment.name(),
Return=(att.CREATED, att.MODIFIED)).on(self._txn))[0]
)
- home = (
- yield self._txn.calendarHomeWithResourceID(
- self.attachment._ownerHomeID))
if home:
# Adjust quota
- yield home.adjustQuotaUsedBytes(self.attachment.size() - old_size)
+ yield home.adjustQuotaUsedBytes(self._attachment.size() - oldSize)
# Send change notification to home
yield home.notifyChanged()
@@ -794,12 +840,13 @@
implements(IAttachment)
- def __init__(self, txn, dropboxID, name, ownerHomeID=None):
+ def __init__(self, txn, dropboxID, name, ownerHomeID=None, justCreated=False):
self._txn = txn
self._dropboxID = dropboxID
self._name = name
self._ownerHomeID = ownerHomeID
self._size = 0
+ self._justCreated = justCreated
@classmethod
@@ -823,7 +870,7 @@
pass
# Now create the DB entry
- attachment = cls(txn, dropboxID, name, ownerHomeID)
+ attachment = cls(txn, dropboxID, name, ownerHomeID, True)
att = schema.ATTACHMENT
yield Insert({
att.CALENDAR_HOME_RESOURCE_ID : ownerHomeID,
@@ -838,8 +885,8 @@
@classmethod
@inlineCallbacks
- def attachmentWithName(cls, txn, dropboxID, name):
- attachment = Attachment(txn, dropboxID, name)
+ def loadWithName(cls, txn, dropboxID, name):
+ attachment = cls(txn, dropboxID, name)
attachment = (yield attachment.initFromStore())
returnValue(attachment)
@@ -886,7 +933,7 @@
def store(self, contentType):
- return AttachmentStorageTransport(self, contentType)
+ return AttachmentStorageTransport(self, contentType, self._justCreated)
def retrieve(self, protocol):
@@ -903,19 +950,28 @@
@inlineCallbacks
def remove(self):
- old_size = self._size
+ oldSize = self._size
self._txn.postCommit(self._path.remove)
- yield self._removeStatement.on(self._txn, dropboxID=self._dropboxID,
- path=self._name)
+ yield self._internalRemove()
# Adjust quota
home = (yield self._txn.calendarHomeWithResourceID(self._ownerHomeID))
if home:
- yield home.adjustQuotaUsedBytes(-old_size)
+ yield home.adjustQuotaUsedBytes(-oldSize)
# Send change notification to home
yield home.notifyChanged()
+ def _internalRemove(self):
+ """
+ Just delete the row; don't do any accounting / bookkeeping. (This is
+ for attachments that have failed to be created due to errors during
+ storage.)
+ """
+ return self._removeStatement.on(self._txn, dropboxID=self._dropboxID,
+ path=self._name)
+
+
# IDataStoreObject
def contentType(self):
return self._contentType
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/common.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/common.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,6 +1,6 @@
# -*- test-case-name: txdav.caldav.datastore -*-
##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 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.
@@ -19,6 +19,8 @@
Tests for common calendar store API functions.
"""
+from StringIO import StringIO
+
from twisted.internet.defer import Deferred, inlineCallbacks, returnValue,\
maybeDeferred
from twisted.internet.protocol import Protocol
@@ -49,6 +51,9 @@
from twistedcaldav.customxml import InviteNotification, InviteSummary
+from txdav.caldav.icalendarstore import IAttachmentStorageTransport
+from txdav.caldav.icalendarstore import QuotaExceeded
+from txdav.common.datastore.test.util import deriveQuota
from twistedcaldav.ical import Component
storePath = FilePath(__file__).parent().child("calendar_store")
@@ -860,10 +865,14 @@
self.assertIsInstance(calendar.created(), int)
self.assertIsInstance(calendar.modified(), int)
- self.assertEqual(calendar.accessMode, CommonTests.metadata1["accessMode"])
- self.assertEqual(calendar.isScheduleObject, CommonTests.metadata1["isScheduleObject"])
- self.assertEqual(calendar.scheduleEtags, CommonTests.metadata1["scheduleEtags"])
- self.assertEqual(calendar.hasPrivateComment, CommonTests.metadata1["hasPrivateComment"])
+ self.assertEqual(calendar.accessMode,
+ CommonTests.metadata1["accessMode"])
+ self.assertEqual(calendar.isScheduleObject,
+ CommonTests.metadata1["isScheduleObject"])
+ self.assertEqual(calendar.scheduleEtags,
+ CommonTests.metadata1["scheduleEtags"])
+ self.assertEqual(calendar.hasPrivateComment,
+ CommonTests.metadata1["hasPrivateComment"])
calendar.accessMode = Component.ACCESS_PRIVATE
calendar.isScheduleObject = True
@@ -912,13 +921,116 @@
self.assertEquals(component.resourceUID(), "uid1")
+ perUserComponent = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T120100Z
+DTSTAMP:20080601T120000Z
+UID:event-with-some-per-user-data
+ATTENDEE:urn:uuid:home1
+ORGANIZER:urn:uuid:home1
+END:VEVENT
+BEGIN:X-CALENDARSERVER-PERUSER
+X-CALENDARSERVER-PERUSER-UID:some-other-user
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:somebody else
+TRIGGER:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+BEGIN:X-CALENDARSERVER-PERUSER
+X-CALENDARSERVER-PERUSER-UID:home1
+BEGIN:X-CALENDARSERVER-PERINSTANCE
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:the owner
+TRIGGER:-PT20M
+END:VALARM
+END:X-CALENDARSERVER-PERINSTANCE
+END:X-CALENDARSERVER-PERUSER
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
+ asSeenByOwner = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T120100Z
+DTSTAMP:20080601T120000Z
+UID:event-with-some-per-user-data
+ATTENDEE:urn:uuid:home1
+ORGANIZER:urn:uuid:home1
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:the owner
+TRIGGER:-PT20M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
+ asSeenByOther = lambda self: VComponent.fromString("""BEGIN:VCALENDAR
+PRODID:-//CALENDARSERVER.ORG//NONSGML Version 1//EN
+VERSION:2.0
+BEGIN:VEVENT
+DTSTART:20110101T120000Z
+DTEND:20110101T120100Z
+DTSTAMP:20080601T120000Z
+UID:event-with-some-per-user-data
+ATTENDEE:urn:uuid:home1
+ORGANIZER:urn:uuid:home1
+BEGIN:VALARM
+ACTION:DISPLAY
+DESCRIPTION:somebody else
+TRIGGER:-PT20M
+END:VALARM
+END:VEVENT
+END:VCALENDAR
+""".replace("\n", "\r\n"))
+
+
@inlineCallbacks
+ def setUpPerUser(self):
+ """
+ Set up state for testing of per-user components.
+ """
+ cal = yield self.calendarUnderTest()
+ yield cal.createCalendarObjectWithName(
+ "per-user-stuff.ics",
+ self.perUserComponent())
+ returnValue((yield cal.calendarObjectWithName("per-user-stuff.ics")))
+
+
+ @inlineCallbacks
+ def test_filteredComponent(self):
+ """
+ L{ICalendarObject.filteredComponent} returns a L{VComponent} that has
+ filtered per-user data.
+ """
+ obj = yield self.setUpPerUser()
+ temp = yield obj.component()
+ obj._component = temp.duplicate()
+ otherComp = (yield obj.filteredComponent("some-other-user"))
+ self.assertEquals(otherComp, self.asSeenByOther())
+ obj._component = temp.duplicate()
+ ownerComp = (yield obj.filteredComponent("home1"))
+ self.assertEquals(ownerComp, self.asSeenByOwner())
+
+
+ @inlineCallbacks
def test_iCalendarText(self):
"""
L{ICalendarObject.iCalendarText} returns a C{str} describing the same
data provided by L{ICalendarObject.component}.
"""
- text = yield (yield self.calendarObjectUnderTest()).iCalendarText()
+ text = yield (yield self.calendarObjectUnderTest())._text()
self.assertIsInstance(text, str)
self.failUnless(text.startswith("BEGIN:VCALENDAR\r\n"))
self.assertIn("\r\nUID:uid1\r\n", text)
@@ -1247,7 +1359,7 @@
(yield self.calendarObjectUnderTest()).properties()[propertyName],
propertyContent)
obj = yield self.calendarObjectUnderTest()
- event1_text = yield obj.iCalendarText()
+ event1_text = yield obj._text()
event1_text_withDifferentSubject = event1_text.replace(
"SUMMARY:CalDAV protocol updates",
"SUMMARY:Changed"
@@ -1329,7 +1441,7 @@
now that logic lives in the protocol layer, so this testing method
replicates it.
"""
- uuid, rev = token.split("_", 1)
+ _ignore_uuid, rev = token.split("_", 1)
rev = int(rev)
return rev
@@ -1442,6 +1554,7 @@
"new.attachment",
)
t = attachment.store(MimeType("text", "x-fixture"))
+ self.assertProvides(IAttachmentStorageTransport, t)
t.write("new attachment")
t.write(" text")
yield t.loseConnection()
@@ -1492,6 +1605,108 @@
return self.createAttachmentTest(refresh)
+ @inlineCallbacks
+ def test_quotaAllowedBytes(self):
+ """
+ L{ICalendarHome.quotaAllowedBytes} should return the configuration value
+ passed to the calendar store's constructor.
+ """
+ expected = deriveQuota(self.id())
+ home = yield self.homeUnderTest()
+ actual = home.quotaAllowedBytes()
+ self.assertEquals(expected, actual)
+
+
+ @inlineCallbacks
+ def test_quotaTransportAddress(self):
+ """
+ Since L{IAttachmentStorageTransport} is a subinterface of L{ITransport},
+ it must provide peer and host addresses.
+ """
+ obj = yield self.calendarObjectUnderTest()
+ name = 'a-fun-attachment'
+ attachment = yield obj.createAttachmentWithName(name)
+ transport = attachment.store(MimeType("test", "x-something"))
+ peer = transport.getPeer()
+ host = transport.getHost()
+ self.assertIdentical(peer.attachment, attachment)
+ self.assertIdentical(host.attachment, attachment)
+ self.assertIn(name, repr(peer))
+ self.assertIn(name, repr(host))
+
+
+ @inlineCallbacks
+ def exceedQuotaTest(self, getit):
+ """
+ If too many bytes are passed to the transport returned by
+ L{ICalendarObject.createAttachmentWithName},
+ L{IAttachmentStorageTransport.loseConnection} will return a L{Deferred}
+ that fails with L{QuotaExceeded}.
+ """
+ home = yield self.homeUnderTest()
+ attachment = yield getit()
+ t = attachment.store(MimeType("text", "x-fixture"))
+ sample = "all work and no play makes jack a dull boy"
+ chunk = (sample * (home.quotaAllowedBytes() / len(sample)))
+
+ t.write(chunk)
+ t.writeSequence([chunk, chunk])
+
+ d = t.loseConnection()
+ yield self.failUnlessFailure(d, QuotaExceeded)
+
+
+ @inlineCallbacks
+ def test_exceedQuotaNew(self):
+ """
+ When quota is exceeded on a new attachment, that attachment will no
+ longer exist.
+ """
+ obj = yield self.calendarObjectUnderTest()
+ yield self.exceedQuotaTest(
+ lambda: obj.createAttachmentWithName("too-big.attachment")
+ )
+ self.assertEquals((yield obj.attachments()), [])
+ yield self.commit()
+ obj = yield self.calendarObjectUnderTest()
+ self.assertEquals((yield obj.attachments()), [])
+
+
+ @inlineCallbacks
+ def test_exceedQuotaReplace(self):
+ """
+ When quota is exceeded while replacing an attachment, that attachment's
+ contents will not be replaced.
+ """
+ obj = yield self.calendarObjectUnderTest()
+ create = lambda: obj.createAttachmentWithName("exists.attachment")
+ get = lambda: obj.attachmentWithName("exists.attachment")
+ attachment = yield create()
+ t = attachment.store(MimeType("text", "x-fixture"))
+ sampleData = "a reasonably sized attachment"
+ t.write(sampleData)
+ yield t.loseConnection()
+ yield self.exceedQuotaTest(get)
+ def checkOriginal():
+ catch = StringIO()
+ catch.dataReceived = catch.write
+ lost = []
+ catch.connectionLost = lost.append
+ attachment.retrieve(catch)
+ expected = sampleData
+ # note: 60 is less than len(expected); trimming is just to make
+ # the error message look sane when the test fails.
+ actual = catch.getvalue()[:60]
+ self.assertEquals(actual, expected)
+ checkOriginal()
+ yield self.commit()
+ # Make sure that things go back to normal after a commit of that
+ # transaction.
+ obj = yield self.calendarObjectUnderTest()
+ attachment = yield get()
+ checkOriginal()
+
+
def test_removeAttachmentWithName(self, refresh=lambda x:x):
"""
L{ICalendarObject.removeAttachmentWithName} will remove the calendar
@@ -1555,7 +1770,7 @@
yield self.calendarObjectUnderTest()
txn = self.lastTransaction
yield self.commit()
-
+
yield self.failUnlessFailure(
maybeDeferred(txn.commit),
AlreadyFinishedError
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -38,6 +38,7 @@
from txdav.caldav.datastore.file import CalendarStore, CalendarHome
from txdav.caldav.datastore.file import Calendar, CalendarObject
+from txdav.common.datastore.test.util import deriveQuota
from txdav.caldav.datastore.test.common import (
CommonTests, event4_text, event1modified_text)
@@ -64,8 +65,10 @@
calendarPath.parent().makedirs()
storePath.copyTo(calendarPath)
- test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory)
- test.txn = test.calendarStore.newTransaction(test.id() + "(old)")
+ testID = test.id()
+ test.calendarStore = CalendarStore(storeRootPath, test.notifierFactory,
+ quota=deriveQuota(testID))
+ test.txn = test.calendarStore.newTransaction(testID + "(old)")
assert test.calendarStore is not None, "No calendar store?"
@@ -77,6 +80,7 @@
assert test.home1 is not None, "No calendar home?"
+
@inlineCallbacks
def setUpCalendar1(test):
yield setUpHome1(test)
@@ -375,23 +379,6 @@
)
- @inlineCallbacks
- def test_modifyCalendarObjectCaches(self):
- """
- Modifying a calendar object should cache the modified component in
- memory, to avoid unnecessary parsing round-trips.
- """
- self.addCleanup(self.txn.commit)
- modifiedComponent = VComponent.fromString(event1modified_text)
- (yield self.calendar1.calendarObjectWithName("1.ics")).setComponent(
- modifiedComponent
- )
- self.assertIdentical(
- modifiedComponent,
- (yield self.calendar1.calendarObjectWithName("1.ics")).component()
- )
-
-
@featureUnimplemented
def test_removeCalendarObjectWithUID_absent(self):
"""
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -49,7 +49,8 @@
try:
component = Component.fromString(text)
# Fix any bogus data we can
- component.validateComponentsForCalDAV(False, fix=True)
+ component.validCalendarData()
+ component.validCalendarForCalDAV(methodAllowed=False)
except InvalidICalendarDataError, e:
raise InternalDataStoreError(
"File corruption detected (%s) in file: %s"
@@ -163,6 +164,7 @@
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -182,6 +184,7 @@
UID:12345-67890-2.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -202,6 +205,7 @@
UID:12345-67890-2.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -212,6 +216,7 @@
RECURRENCE-ID:20080608T120000Z
DTSTART:20080608T120000Z
DTEND:20080608T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -231,6 +236,7 @@
UID:12345-67890-2.3
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -241,6 +247,7 @@
RECURRENCE-ID:20080609T120000Z
DTSTART:20080608T120000Z
DTEND:20080608T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -260,6 +267,7 @@
UID:12345-67890-2.4
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -270,6 +278,7 @@
RECURRENCE-ID:20080609T120000Z
DTSTART:20080608T120000Z
DTEND:20080608T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -322,6 +331,7 @@
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -342,6 +352,7 @@
UID:12345-67890-1.2
DTSTART:20080602T120000Z
DTEND:20080602T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -363,6 +374,7 @@
UID:12345-67890-1.3
DTSTART:20080603T120000Z
DTEND:20080603T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -384,6 +396,7 @@
UID:12345-67890-1.4
DTSTART:20080604T120000Z
DTEND:20080604T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -405,6 +418,7 @@
UID:12345-67890-2.1
DTSTART:20080605T120000Z
DTEND:20080605T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -429,6 +443,7 @@
UID:12345-67890-2.2
DTSTART:20080607T120000Z
DTEND:20080607T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -439,6 +454,7 @@
RECURRENCE-ID:20080608T120000Z
DTSTART:20080608T140000Z
DTEND:20080608T150000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -502,6 +518,7 @@
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -543,6 +560,7 @@
UID:12345-67890-1.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -599,6 +617,7 @@
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -647,6 +666,7 @@
UID:12345-67890-1.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -713,6 +733,7 @@
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -723,6 +744,7 @@
RECURRENCE-ID:20080602T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -776,6 +798,7 @@
UID:12345-67890-1.2
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -786,6 +809,7 @@
RECURRENCE-ID:20080602T120000Z
DTSTART:20080602T130000Z
DTEND:20080602T140000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -899,6 +923,7 @@
UID:12345-67890-1.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -912,6 +937,7 @@
UID:12345-67890-2.1
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
@@ -926,6 +952,7 @@
UID:12345-67890-2.3
DTSTART:20080601T120000Z
DTEND:20080601T130000Z
+DTSTAMP:20080601T120000Z
ORGANIZER;CN="User 01":mailto:user1 at example.com
ATTENDEE:mailto:user1 at example.com
ATTENDEE:mailto:user2 at example.com
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
+ /CalendarServer/branches/config-separation/txdav/caldav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/caldav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/caldav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/caldav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/caldav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/caldav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/caldav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/caldav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/caldav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/caldav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/caldav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/caldav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/caldav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/caldav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/caldav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/caldav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/caldav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/caldav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/caldav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/caldav/datastore/test/test_index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/caldav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/caldav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/caldav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/caldav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/caldav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/caldav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/caldav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/caldav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/caldav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/caldav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/caldav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/caldav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/caldav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/caldav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/caldav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/caldav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/caldav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/caldav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/caldav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/caldav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/caldav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_index.py:6322-6394
/CalendarServer/trunk/txdav/caldav/datastore/test/test_index_file.py:7443-7695
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_scheduling.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_scheduling.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_scheduling.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -53,7 +53,7 @@
def storeUnderTest(self):
if self.implicitStore is None:
- sut = FileStorageTests.storeUnderTest(self)
+ sut = super(ImplicitStoreTests, self).storeUnderTest()
self.implicitStore = ImplicitStore(sut)
return self.implicitStore
@@ -62,3 +62,5 @@
test_calendarObjectsWithDotFile = skipit
test_init = skipit
+
+del FileStorageTests
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_sql.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/test/test_sql.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -38,8 +38,8 @@
from txdav.common.datastore.sql_tables import schema
from txdav.common.datastore.test.util import buildStore, populateCalendarsFrom
-from twistedcaldav import memcacher, caldavxml
-from twistedcaldav.config import config
+from twistedcaldav import caldavxml
+
from twistedcaldav.dateops import datetimeMktime
from twistedcaldav.sharing import SharedCollectionRecord
@@ -52,10 +52,6 @@
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(memcacher.Memcacher, "allowTestCache", True)
-
yield super(CalendarSQLStorageTests, self).setUp()
self._sqlCalendarStore = yield buildStore(self, self.notifierFactory)
yield self.populate()
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/datastore/util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -28,6 +28,10 @@
from txdav.common.icommondatastore import InvalidObjectResourceError, \
NoSuchObjectResourceError, InternalDataStoreError
+from twistedcaldav.datafilters.privateevents import PrivateEventFilter
+from twistedcaldav.datafilters.peruserdata import PerUserDataFilter
+from zope.interface.declarations import implements
+from txdav.caldav.icalendarstore import IAttachmentStorageTransport
from twext.python.log import Logger
log = Logger()
@@ -60,12 +64,10 @@
pass
try:
- # FIXME: This is a bad way to do this test, there should be a
+ # FIXME: This is a bad way to do this test (== 'inbox'), there should be a
# Calendar-level API for it.
- if calendar.name() == 'inbox':
- component.validateComponentsForCalDAV(True)
- else:
- component.validateForCalDAV()
+ component.validCalendarData()
+ component.validCalendarForCalDAV(methodAllowed=calendar.name() == 'inbox')
except InvalidICalendarDataError, e:
raise InvalidObjectResourceError(e)
@@ -215,3 +217,105 @@
# No migration for notifications, since they weren't present in earlier
# released versions of CalendarServer.
+
+class CalendarObjectBase(object):
+ """
+ Base logic shared between file- and sql-based L{ICalendarObject}
+ implementations.
+ """
+
+ @inlineCallbacks
+ def filteredComponent(self, accessUID, asAdmin=False):
+ """
+ Filter this calendar object's iCalendar component as it would be
+ perceived by a particular user, accounting for per-user iCalendar data
+ and private events, and return a L{Deferred} that fires with that
+ object.
+
+ Unlike the result of C{component()}, which contains storage-specific
+ iCalendar properties, this is a valid iCalendar object which could be
+ serialized and displayed to other iCalendar-processing software.
+
+ @param accessUID: the UID of the principal who is accessing this
+ component.
+ @type accessUID: C{str} (UTF-8 encoded)
+
+ @param asAdmin: should the given UID be treated as an administrator? If
+ this is C{True}, the resulting component will have an unobscured
+ view of private events, even if the given UID is not actually the
+ owner of said events. (However, per-instance overridden values will
+ still be seen as the given C{accessUID}.)
+
+ @return: a L{Deferred} which fires with a
+ L{twistedcaldav.ical.Component}.
+ """
+ component = yield self.component()
+ calendar = self.calendar()
+ isOwner = asAdmin or (calendar._owned and
+ calendar.ownerCalendarHome().uid() == accessUID)
+ for filter in [PrivateEventFilter(self.accessMode, isOwner),
+ PerUserDataFilter(accessUID)]:
+ component = filter.filter(component)
+ returnValue(component)
+
+
+
+class StorageTransportAddress(object):
+ """
+ Peer / host address for L{IAttachmentStorageTransport} implementations.
+
+ @ivar attachment: the L{IAttachment} being stored.
+
+ @type attachment: L{IAttachment} provider
+
+ @ivar isHost: is this a host address or peer address?
+
+ @type isHost: C{bool}
+ """
+
+ def __init__(self, attachment, isHost):
+ """
+ Initialize with the attachment being stored.
+ """
+ self.attachment = attachment
+ self.isHost = isHost
+
+
+ def __repr__(self):
+ if self.isHost:
+ host = " (host)"
+ else:
+ host = ""
+ return '<Storing Attachment: %r%s>' % (self.attachment.name(), host)
+
+
+
+class StorageTransportBase(object):
+ """
+ Base logic shared between file- and sql-based L{IAttachmentStorageTransport}
+ implementations.
+ """
+
+ implements(IAttachmentStorageTransport)
+
+ def __init__(self, attachment, contentType):
+ """
+ Create a storage transport with a reference to an L{IAttachment} and a
+ L{twext.web2.http_headers.MimeType}.
+ """
+ self._attachment = attachment
+ self._contentType = contentType
+
+
+ def getPeer(self):
+ return StorageTransportAddress(self._attachment, False)
+
+
+ def getHost(self):
+ return StorageTransportAddress(self._attachment, True)
+
+
+ def writeSequence(self, seq):
+ return self.write(''.join(seq))
+
+
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/icalendarstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/icalendarstore.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/caldav/icalendarstore.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,6 +1,6 @@
# -*- test-case-name: txdav.caldav.datastore -*-
##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 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.
@@ -23,18 +23,30 @@
IShareableCollection
from txdav.idav import IDataStoreObject, IDataStore
+from twisted.internet.interfaces import ITransport
from txdav.idav import INotifier
__all__ = [
- # Classes
+ # Interfaces
"ICalendarTransaction",
"ICalendarHome",
"ICalendar",
"ICalendarObject",
+
+ # Exceptions
+ "QuotaExceeded",
]
+
+class QuotaExceeded(Exception):
+ """
+ The quota for a particular user has been exceeded.
+ """
+
+
+
class ICalendarTransaction(ICommonTransaction):
"""
Transaction functionality required to be implemented by calendar stores.
@@ -163,15 +175,34 @@
"""
+ def quotaAllowedBytes():
+ """
+ The number of bytes of quota that the user is allowed to access.
+
+ Currently this is only enforced / tracked against attachment data.
+
+ @rtype: C{int}
+ """
+
+
def quotaUsedBytes():
"""
The number of bytes counted towards the user's quota.
+
+ @rtype: C{int}
"""
def adjustQuotaUsedBytes(delta):
"""
- Increase the number of bytes that count towards the user's quota.
+ Increase or decrease the number of bytes that count towards the user's
+ quota.
+
+ @param delta: The number of bytes to adjust the quota by.
+
+ @type delta: C{int}
+
+ @raise QuotaExceeded: when the quota is exceeded.
"""
@@ -336,14 +367,6 @@
@return: a C{VCALENDAR} L{VComponent}.
"""
- def iCalendarText():
- """
- Retrieve the iCalendar text data for this calendar object.
-
- @return: a string containing iCalendar data for a single
- calendar object.
- """
-
def uid():
"""
Retrieve the UID for this calendar object.
@@ -395,12 +418,18 @@
def attachmentWithName(name):
"""
- Retrieve an attachment from this calendar object.
+ Asynchronously retrieve an attachment with the given name from this
+ calendar object.
@param name: An identifier, unique to this L{ICalendarObject}, which
names the attachment for future retrieval.
@type name: C{str}
+
+ @return: a L{Deferred} which fires with an L{IAttachment} with the given
+ name, or L{None} if no such attachment exists.
+
+ @rtype: L{Deferred}
"""
# FIXME: MIME-type?
@@ -432,6 +461,36 @@
+class IAttachmentStorageTransport(ITransport):
+ """
+ An L{IAttachmentStorageTransport} is a transport which stores the bytes
+ written to in a calendar attachment.
+
+ The user of an L{IAttachmentStorageTransport} must call C{loseConnection} on
+ its result to indicate that the attachment upload was successfully
+ completed. If the transaction associated with this upload is committed or
+ aborted before C{loseConnection} is called, the upload will be presumed to
+ have failed, and no attachment data will be stored.
+ """
+
+ # Note: should also require IConsumer
+
+ def loseConnection():
+ """
+ The attachment has completed being uploaded successfully.
+
+ Unlike L{ITransport.loseConnection}, which returns C{None}, providers of
+ L{IAttachmentStorageTransport} must return a L{Deferred} from
+ C{loseConnection}, which may fire with a few different types of error;
+ for example, it may fail with a L{QuotaExceeded}.
+
+ If the upload fails for some reason, the transaction should be
+ terminated with L{ICalendarTransaction.abort} and this method should
+ never be called.
+ """
+
+
+
class IAttachment(IDataStoreObject):
"""
Information associated with an attachment to a calendar object.
@@ -439,17 +498,15 @@
def store(contentType):
"""
+ Store an attachment (of the given MIME content/type).
+
@param contentType: The content type of the data which will be stored.
+
@type contentType: L{twext.web2.http_headers.MimeType}
- @return: An L{ITransport}/L{IConsumer} provider that will store the
- bytes passed to its 'write' method.
+ @return: A transport which stores the contents written to it.
- The caller of C{store} must call C{loseConnection} on its result to
- indicate that the attachment upload was successfully completed. If
- the transaction associated with this upload is committed or aborted
- before C{loseConnection} is called, the upload will be presumed to
- have failed, and no attachment data will be stored.
+ @rtype: L{IAttachmentStorageTransport}
"""
# If you do a big write()/loseConnection(), how do you tell when the
# data has actually been written? you don't: commit() ought to return
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -144,9 +144,6 @@
),
)
- def _doValidate(self, component):
- component.validForCardDAV()
-
def contentType(self):
"""
The content type of Addresbook objects is text/vcard.
@@ -181,8 +178,8 @@
self.name(), component
)
- self._component = component
- # FIXME: needs to clear text cache
+ componentText = str(component)
+ self._objectText = componentText
def do():
# Mark all properties as dirty, so they can be added back
@@ -194,7 +191,6 @@
backup = hidden(self._path.temporarySibling())
self._path.moveTo(backup)
- componentText = str(component)
fh = self._path.open("w")
try:
# FIXME: concurrency problem; if this write is interrupted
@@ -221,23 +217,38 @@
def component(self):
- if self._component is not None:
- return self._component
- text = self.text()
-
+ """
+ Read address data and validate/fix it. Do not raise a store error here if there are unfixable
+ errors as that could prevent the overall request to fail. Instead we will hand bad data off to
+ the caller - that is not ideal but in theory we should have checked everything on the way in and
+ only allowed in good data.
+ """
+ text = self._text()
try:
component = VComponent.fromString(text)
except InvalidVCardDataError, e:
+ # This is a really bad situation, so do raise
raise InternalDataStoreError(
"File corruption detected (%s) in file: %s"
% (e, self._path.path)
)
+
+ # Fix any bogus data we can
+ fixed, unfixed = component.validVCardData(doFix=True, doRaise=False)
+
+ if unfixed:
+ self.log_error("Address data at %s had unfixable problems:\n %s" % (self._path.path, "\n ".join(unfixed),))
+
+ if fixed:
+ self.log_error("Address data at %s had fixable problems:\n %s" % (self._path.path, "\n ".join(fixed),))
+
return component
- def text(self):
- if self._component is not None:
- return str(self._component)
+ def _text(self):
+ if self._objectText is not None:
+ return self._objectText
+
try:
fh = self._path.open()
except IOError, e:
@@ -259,10 +270,10 @@
"File corruption detected (improper start) in file: %s"
% (self._path.path,)
)
+
+ self._objectText = text
return text
- vCardText = text
-
def uid(self):
if not hasattr(self, "_uid"):
self._uid = self.component().resourceUID()
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -589,6 +589,7 @@
# FIXME: This is blocking I/O
try:
vcard = Component.fromStream(stream)
+ vcard.validVCardData()
vcard.validForCardDAV()
except ValueError:
log.err("Non-addressbook resource: %s" % (name,))
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
+ /CalendarServer/branches/config-separation/txdav/carddav/datastore/index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/carddav/datastore/index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/vcardindex.py:6322-6394
/CalendarServer/trunk/txdav/carddav/datastore/index_file.py:7443-7695
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/sql.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/sql.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
##
+from txdav.common.icommondatastore import InternalDataStoreError
"""
SQL backend for CardDAV storage.
@@ -35,7 +36,7 @@
from twistedcaldav import carddavxml, customxml
from twistedcaldav.memcacher import Memcacher
-from twistedcaldav.vcard import Component as VCard
+from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
from txdav.common.datastore.sql_legacy import \
PostgresLegacyABIndexEmulator, SQLLegacyAddressBookInvites,\
@@ -177,10 +178,6 @@
)
- def _doValidate(self, component):
- component.validForCardDAV()
-
-
def contentType(self):
"""
The content type of Addresbook objects is text/vcard.
@@ -271,16 +268,39 @@
@inlineCallbacks
def component(self):
- returnValue(VCard.fromString((yield self.vCardText())))
+ """
+ Read address data and validate/fix it. Do not raise a store error here if there are unfixable
+ errors as that could prevent the overall request to fail. Instead we will hand bad data off to
+ the caller - that is not ideal but in theory we should have checked everything on the way in and
+ only allowed in good data.
+ """
+ text = yield self._text()
+ try:
+ component = VCard.fromString(text)
+ except InvalidVCardDataError, e:
+ # This is a really bad situation, so do raise
+ raise InternalDataStoreError(
+ "Data corruption detected (%s) in id: %s"
+ % (e, self._resourceID)
+ )
- vCardText = CommonObjectResource.text
+ # Fix any bogus data we can
+ fixed, unfixed = component.validVCardData(doFix=True, doRaise=False)
+ if unfixed:
+ self.log_error("Address data id=%s had unfixable problems:\n %s" % (self._resourceID, "\n ".join(unfixed),))
+
+ if fixed:
+ self.log_error("Address data id=%s had fixable problems:\n %s" % (self._resourceID, "\n ".join(fixed),))
+ returnValue(component)
+
+
# IDataStoreObject
def contentType(self):
"""
- The content type of Addressbook objects is text/x-vcard.
+ The content type of Addressbook objects is text/vcard.
"""
return MimeType.fromString("text/vcard; charset=utf-8")
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/common.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/common.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/common.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -594,7 +594,7 @@
L{IAddressBookObject.iAddressBookText} returns a C{str} describing the same
data provided by L{IAddressBookObject.component}.
"""
- text = yield (yield self.addressbookObjectUnderTest()).vCardText()
+ text = yield (yield self.addressbookObjectUnderTest())._text()
self.assertIsInstance(text, str)
self.failUnless(text.startswith("BEGIN:VCARD\r\n"))
self.assertIn("\r\nUID:uid1\r\n", text)
@@ -906,7 +906,7 @@
],
propertyContent)
obj = yield self.addressbookObjectUnderTest()
- vcard1_text = yield obj.vCardText()
+ vcard1_text = yield obj._text()
vcard1_text_withDifferentNote = vcard1_text.replace(
"NOTE:CardDAV protocol updates",
"NOTE:Changed"
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_index_file.py
___________________________________________________________________
Modified: svn:mergeinfo
- /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
+ /CalendarServer/branches/config-separation/txdav/carddav/datastore/test/test_index_file.py:4379-4443
/CalendarServer/branches/egg-info-351/txdav/carddav/datastore/test/test_index_file.py:4589-4625
/CalendarServer/branches/generic-sqlstore/txdav/carddav/datastore/test/test_index_file.py:6167-6191
/CalendarServer/branches/new-store-no-caldavfile-2/txdav/carddav/datastore/test/test_index_file.py:5936-5981
/CalendarServer/branches/new-store-no-caldavfile/txdav/carddav/datastore/test/test_index_file.py:5911-5935
/CalendarServer/branches/new-store/txdav/carddav/datastore/test/test_index_file.py:5594-5934
/CalendarServer/branches/users/cdaboo/batchupload-6699/txdav/carddav/datastore/test/test_index_file.py:6700-7198
/CalendarServer/branches/users/cdaboo/cached-subscription-calendars-5692/txdav/carddav/datastore/test/test_index_file.py:5693-5702
/CalendarServer/branches/users/cdaboo/directory-cache-on-demand-3627/txdav/carddav/datastore/test/test_index_file.py:3628-3644
/CalendarServer/branches/users/cdaboo/more-sharing-5591/txdav/carddav/datastore/test/test_index_file.py:5592-5601
/CalendarServer/branches/users/cdaboo/partition-4464/txdav/carddav/datastore/test/test_index_file.py:4465-4957
/CalendarServer/branches/users/cdaboo/pods/txdav/carddav/datastore/test/test_index_file.py:7297-7377
/CalendarServer/branches/users/cdaboo/pycalendar/txdav/carddav/datastore/test/test_index_file.py:7085-7206
/CalendarServer/branches/users/cdaboo/pycard/txdav/carddav/datastore/test/test_index_file.py:7227-7237
/CalendarServer/branches/users/cdaboo/relative-config-paths-5070/txdav/carddav/datastore/test/test_index_file.py:5071-5105
/CalendarServer/branches/users/cdaboo/shared-calendars-5187/txdav/carddav/datastore/test/test_index_file.py:5188-5440
/CalendarServer/branches/users/glyph/conn-limit/txdav/carddav/datastore/test/test_index_file.py:6574-6577
/CalendarServer/branches/users/glyph/contacts-server-merge/txdav/carddav/datastore/test/test_index_file.py:4971-5080
/CalendarServer/branches/users/glyph/dalify/txdav/carddav/datastore/test/test_index_file.py:6932-7023
/CalendarServer/branches/users/glyph/deploybuild/txdav/carddav/datastore/test/test_index_file.py:7563-7572
/CalendarServer/branches/users/glyph/dont-start-postgres/txdav/carddav/datastore/test/test_index_file.py:6592-6614
/CalendarServer/branches/users/glyph/linux-tests/txdav/carddav/datastore/test/test_index_file.py:6893-6900
/CalendarServer/branches/users/glyph/misc-portability-fixes/txdav/carddav/datastore/test/test_index_file.py:7365-7374
/CalendarServer/branches/users/glyph/more-deferreds-6/txdav/carddav/datastore/test/test_index_file.py:6322-6334
/CalendarServer/branches/users/glyph/more-deferreds-7/txdav/carddav/datastore/test/test_index_file.py:6369
/CalendarServer/branches/users/glyph/new-export/txdav/carddav/datastore/test/test_index_file.py:7444-7485
/CalendarServer/branches/users/glyph/oracle-nulls/txdav/carddav/datastore/test/test_index_file.py:7340-7351
/CalendarServer/branches/users/glyph/oracle/txdav/carddav/datastore/test/test_index_file.py:7106-7155
/CalendarServer/branches/users/glyph/sendfdport/txdav/carddav/datastore/test/test_index_file.py:5388-5424
/CalendarServer/branches/users/glyph/sharedpool/txdav/carddav/datastore/test/test_index_file.py:6490-6550
/CalendarServer/branches/users/glyph/sql-store/txdav/carddav/datastore/test/test_index_file.py:5929-6073
/CalendarServer/branches/users/glyph/subtransactions/txdav/carddav/datastore/test/test_index_file.py:7248-7258
/CalendarServer/branches/users/glyph/use-system-twisted/txdav/carddav/datastore/test/test_index_file.py:5084-5149
/CalendarServer/branches/users/sagen/inboxitems/txdav/carddav/datastore/test/test_index_file.py:7380-7381
/CalendarServer/branches/users/sagen/locations-resources-2/txdav/carddav/datastore/test/test_index_file.py:5052-5061
/CalendarServer/branches/users/sagen/locations-resources/txdav/carddav/datastore/test/test_index_file.py:5032-5051
/CalendarServer/branches/users/sagen/purge_old_events/txdav/carddav/datastore/test/test_index_file.py:6735-6746
/CalendarServer/branches/users/sagen/resource-delegates-4038/txdav/carddav/datastore/test/test_index_file.py:4040-4067
/CalendarServer/branches/users/sagen/resource-delegates-4066/txdav/carddav/datastore/test/test_index_file.py:4068-4075
/CalendarServer/branches/users/sagen/resources-2/txdav/carddav/datastore/test/test_index_file.py:5084-5093
/CalendarServer/branches/users/wsanchez/transations/txdav/carddav/datastore/test/test_index_file.py:5515-5593
/CalendarServer/trunk/twistedcaldav/test/test_vcardindex.py:6322-6394
/CalendarServer/trunk/txdav/carddav/datastore/test/test_index_file.py:7443-7695
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_sql.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/test/test_sql.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -25,8 +25,8 @@
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.trial import unittest
-from twistedcaldav import memcacher, carddavxml
-from twistedcaldav.config import config
+from twistedcaldav import carddavxml
+
from twistedcaldav.vcard import Component as VCard
from twistedcaldav.vcard import Component as VComponent
@@ -47,10 +47,6 @@
@inlineCallbacks
def setUp(self):
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(memcacher.Memcacher, "allowTestCache", True)
-
yield super(AddressBookSQLStorageTests, self).setUp()
self._sqlStore = yield buildStore(self, self.notifierFactory)
yield self.populate()
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/datastore/util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -58,6 +58,7 @@
pass
try:
+ component.validVCardData()
component.validForCardDAV()
except InvalidVCardDataError, e:
raise InvalidObjectResourceError(e)
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/iaddressbookstore.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/carddav/iaddressbookstore.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -1,6 +1,6 @@
# -*- test-case-name: txdav.carddav.datastore,txdav.carddav.datastore.test.test_sql.AddressBookSQLStorageTests -*-
##
-# Copyright (c) 2010 Apple Inc. All rights reserved.
+# Copyright (c) 2010-2011 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.
@@ -257,14 +257,6 @@
@return: a C{VCARD} L{VComponent}.
"""
- def vCardText():
- """
- Retrieve the vCard text data for this addressbook object.
-
- @return: a string containing vCard data for a single
- addressbook object.
- """
-
def uid():
"""
Retrieve the UID for this addressbook object.
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/common
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Property changes on: CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore
___________________________________________________________________
Added: svn:ignore
+ *.pyc
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/file.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/file.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/file.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -75,15 +75,21 @@
class CommonDataStore(DataStore):
"""
- An implementation of data store.
+ Shared logic for SQL-based data stores, between calendar and addressbook
+ storage.
@ivar _path: A L{CachingFilePath} referencing a directory on disk that
stores all calendar and addressbook data for a group of UIDs.
+
+ @ivar quota: the amount of space granted to each calendar home (in bytes)
+ for storing attachments.
+
+ @type quota: C{int}
"""
implements(ICalendarStore)
def __init__(self, path, notifierFactory, enableCalendars=True,
- enableAddressBooks=True):
+ enableAddressBooks=True, quota=(2 ** 20)):
"""
Create a store.
@@ -96,6 +102,7 @@
self.enableAddressBooks = enableAddressBooks
self._notifierFactory = notifierFactory
self._transactionClass = CommonStoreTransaction
+ self.quota = quota
def newTransaction(self, name='no name', migrating=False):
@@ -250,6 +257,10 @@
self._cachedChildren = {}
+ def quotaAllowedBytes(self):
+ return self._transaction.store().quota
+
+
@classmethod
def homeWithUID(cls, txn, uid, create=False, withNotifications=False):
@@ -846,9 +857,6 @@
"""
pass
- def _doValidate(self, component):
- raise NotImplementedError
-
def addNotifier(self, notifier):
if self._notifiers is None:
self._notifiers = ()
@@ -895,7 +903,7 @@
self._name = name
self._parentCollection = parent
self._transaction = parent._transaction
- self._component = None
+ self._objectText = None
@property
@@ -916,7 +924,7 @@
raise NotImplementedError
- def text(self):
+ def _text(self):
raise NotImplementedError
@@ -1077,12 +1085,7 @@
name = uid + ".xml"
self.removeNotificationObjectWithName(name)
- def _doValidate(self, component):
- # Nothing to do - notifications are always generated internally by the server
- # so they better be valid all the time!
- pass
-
class NotificationObject(CommonObjectResource):
"""
"""
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -93,12 +93,39 @@
}
class CommonDataStore(Service, object):
+ """
+ Shared logic for SQL-based data stores, between calendar and addressbook
+ storage.
+ @ivar sqlTxnFactory: A 0-arg factory callable that produces an
+ L{IAsyncTransaction}.
+
+ @ivar notifierFactory: a L{twistedcaldav.notify.NotifierFactory} (or
+ similar) that produces new notifiers for homes and collections.
+
+ @ivar attachmentsPath: a L{FilePath} indicating a directory where
+ attachments may be stored.
+
+ @ivar enableCalendars: a boolean, C{True} if this data store should provide
+ L{ICalendarStore}, C{False} if not.
+
+ @ivar enableAddressBooks: a boolean, C{True} if this data store should
+ provide L{IAddressbookStore}, C{False} if not.
+
+ @ivar label: A string, used for tagging debug messages in the case where
+ there is more than one store. (Useful mostly for unit tests.)
+
+ @ivar quota: the amount of space granted to each calendar home (in bytes)
+ for storing attachments.
+
+ @type quota: C{int}
+ """
+
implements(ICalendarStore)
def __init__(self, sqlTxnFactory, notifierFactory, attachmentsPath,
enableCalendars=True, enableAddressBooks=True,
- label="unlabeled"):
+ label="unlabeled", quota=(2 ** 20)):
assert enableCalendars or enableAddressBooks
self.sqlTxnFactory = sqlTxnFactory
@@ -107,6 +134,7 @@
self.enableCalendars = enableCalendars
self.enableAddressBooks = enableAddressBooks
self.label = label
+ self.quota = quota
def eachCalendarHome(self):
@@ -407,7 +435,7 @@
results = (yield self.eventsOlderThan(cutoff, batchSize=batchSize))
count = 0
- for uid, calendarName, eventName, maxDate in results:
+ for uid, calendarName, eventName, _ignore_maxDate in results:
home = (yield self.calendarHomeWithUID(uid))
calendar = (yield home.childWithName(calendarName))
(yield calendar.removeObjectResourceWithName(eventName))
@@ -500,6 +528,10 @@
self._revisionBindJoinTable["BIND:%s" % (key,)] = value
+ def quotaAllowedBytes(self):
+ return self._txn.store().quota
+
+
@classproperty
def _resourceIDFromOwnerQuery(cls): #@NoSelf
home = cls._homeSchema
@@ -1304,6 +1336,10 @@
class CommonHomeChild(LoggingMixIn, FancyEqMixin, _SharedSyncLogic):
"""
Common ancestor class of AddressBooks and Calendars.
+
+ @ivar _owned: Is this calendar or addressbook referencing its sharer (owner)
+ home? (i.e. C{True} if L{ownerCalendarHome} will actually return the
+ sharer, C{False} or if it will return a sharee.)
"""
compareAttributes = (
@@ -2001,10 +2037,6 @@
"""
- def _doValidate(self, component):
- raise NotImplementedError
-
-
# IDataStoreObject
def contentType(self):
raise NotImplementedError()
@@ -2059,9 +2091,7 @@
class CommonObjectResource(LoggingMixIn, FancyEqMixin):
"""
- @ivar _path: The path of the file on disk
-
- @type _path: L{FilePath}
+ Base class for object resources.
"""
compareAttributes = (
@@ -2371,7 +2401,7 @@
@inlineCallbacks
- def text(self):
+ def _text(self):
if self._objectText is None:
text = (
yield self._textByIDQuery.on(self._txn,
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql_legacy.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql_legacy.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/sql_legacy.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -30,13 +30,13 @@
from twext.python.clsprop import classproperty
from twext.python.log import Logger, LoggingMixIn
-from twistedcaldav import carddavxml
from twistedcaldav.config import config
from twistedcaldav.dateops import normalizeForIndex, pyCalendarTodatetime
from twistedcaldav.memcachepool import CachePoolUserMixIn
from twistedcaldav.notifications import NotificationRecord
from twistedcaldav.query import (
- calendarqueryfilter, calendarquery, addressbookquery)
+ calendarqueryfilter, calendarquery, addressbookquery, expression,
+ addressbookqueryfilter)
from twistedcaldav.query.sqlgenerator import sqlgenerator
from twistedcaldav.sharing import Invite
@@ -860,8 +860,23 @@
"""
ISOP = " = "
- CONTAINSOP = " LIKE "
- NOTCONTAINSOP = " NOT LIKE "
+ STARTSWITHOP = ENDSWITHOP = CONTAINSOP = " LIKE "
+ NOTSTARTSWITHOP = NOTENDSWITHOP = NOTCONTAINSOP = " NOT LIKE "
+
+ def containsArgument(self, arg):
+ return "%%%s%%" % (arg,)
+
+ def startswithArgument(self, arg):
+ return "%s%%" % (arg,)
+
+ def endswithArgument(self, arg):
+ return "%%%s" % (arg,)
+
+class CalDAVSQLBehaviorMixin(RealSQLBehaviorMixin):
+ """
+ Query generator for CalDAV indexed searches.
+ """
+
FIELDS = {
"TYPE": "CALENDAR_OBJECT.ICALENDAR_TYPE",
"UID": "CALENDAR_OBJECT.ICALENDAR_UID",
@@ -891,6 +906,62 @@
self.substitutions = []
self.usedtimespan = False
+ # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
+ if self.calendarid:
+
+ test = expression.isExpression("CALENDAR_OBJECT.CALENDAR_RESOURCE_ID", str(self.calendarid), True)
+
+ # Since timerange expression already have the calendar resource-id test in them, do not
+ # add the additional term to those. When the additional term is added, add it as the first
+ # component in the AND expression to hopefully get the DB to use its index first
+
+ # Top-level timerange expression already has calendar resource-id restriction in it
+ if isinstance(self.expression, expression.timerangeExpression):
+ pass
+
+ # Top-level OR - check each component
+ elif isinstance(self.expression, expression.orExpression):
+
+ def _hasTopLevelTimerange(testexpr):
+ if isinstance(testexpr, expression.timerangeExpression):
+ return True
+ elif isinstance(testexpr, expression.andExpression):
+ return any([isinstance(expr, expression.timerangeExpression) for expr in testexpr.expressions])
+ else:
+ return False
+
+ hasTimerange = any([_hasTopLevelTimerange(expr) for expr in self.expression.expressions])
+
+ if hasTimerange:
+ # AND each of the non-timerange expressions
+ trexpressions = []
+ orexpressions = []
+ for expr in self.expression.expressions:
+ if _hasTopLevelTimerange(expr):
+ trexpressions.append(expr)
+ else:
+ orexpressions.append(expr)
+
+ if orexpressions:
+ self.expression.expressions = tuple(trexpressions) + (
+ test.andWith(expression.orExpression(orexpressions)),
+ )
+ else:
+ # AND the whole thing
+ self.expression = test.andWith(self.expression)
+
+
+ # Top-level AND - only add additional expression if timerange not present
+ elif isinstance(self.expression, expression.andExpression):
+ hasTimerange = any([isinstance(expr, expression.timerangeExpression) for expr in self.expression.expressions])
+ if not hasTimerange:
+ # AND the whole thing
+ self.expression = test.andWith(self.expression)
+
+ # Just AND the entire thing
+ else:
+ self.expression = test.andWith(self.expression)
+
# Generate ' where ...' partial statement
self.sout.write(self.WHERE)
self.generateExpression(self.expression)
@@ -911,11 +982,6 @@
return select, self.arguments
- def containsArgument(self, arg):
- return "%%%s%%" % (arg,)
-
-
-
class FormatParamStyleMixin(object):
"""
Mixin for overriding methods on sqlgenerator that generate arguments
@@ -940,7 +1006,7 @@
-class postgresqlgenerator(FormatParamStyleMixin, RealSQLBehaviorMixin,
+class postgresqlgenerator(FormatParamStyleMixin, CalDAVSQLBehaviorMixin,
sqlgenerator):
"""
Query generator for PostgreSQL indexed searches.
@@ -952,17 +1018,17 @@
-class oraclesqlgenerator(RealSQLBehaviorMixin, sqlgenerator):
+class oraclesqlgenerator(CalDAVSQLBehaviorMixin, sqlgenerator):
"""
Query generator for Oracle indexed searches.
"""
- TIMESPANTEST = fixbools(RealSQLBehaviorMixin.TIMESPANTEST)
- TIMESPANTEST_NOEND = fixbools(RealSQLBehaviorMixin.TIMESPANTEST_NOEND)
- TIMESPANTEST_NOSTART = fixbools(RealSQLBehaviorMixin.TIMESPANTEST_NOSTART)
+ TIMESPANTEST = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST)
+ TIMESPANTEST_NOEND = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST_NOEND)
+ TIMESPANTEST_NOSTART = fixbools(CalDAVSQLBehaviorMixin.TIMESPANTEST_NOSTART)
TIMESPANTEST_TAIL_PIECE = fixbools(
- RealSQLBehaviorMixin.TIMESPANTEST_TAIL_PIECE)
+ CalDAVSQLBehaviorMixin.TIMESPANTEST_TAIL_PIECE)
TIMESPANTEST_JOIN_ON_PIECE = fixbools(
- RealSQLBehaviorMixin.TIMESPANTEST_JOIN_ON_PIECE)
+ CalDAVSQLBehaviorMixin.TIMESPANTEST_JOIN_ON_PIECE)
@@ -1259,23 +1325,16 @@
# CARDDAV
-class oraclesqladbkgenerator(sqlgenerator):
+class CardDAVSQLBehaviorMixin(RealSQLBehaviorMixin):
"""
- Query generator for Oracle indexed searches.
+ Query generator for CardDAV indexed searches.
"""
- ISOP = " = "
- CONTAINSOP = " LIKE "
- NOTCONTAINSOP = " NOT LIKE "
FIELDS = {
"UID": "ADDRESSBOOK_OBJECT.VCARD_UID",
}
RESOURCEDB = "ADDRESSBOOK_OBJECT"
- def containsArgument(self, arg):
- return "%%%s%%" % (arg,)
-
-
def generate(self):
"""
Generate the actual SQL 'where ...' expression from the passed in
@@ -1291,6 +1350,13 @@
self.arguments = []
self.substitutions = []
+ # For SQL data DB we need to restrict the query to just the targeted calendar resource-id if provided
+ if self.calendarid:
+
+ # AND the whole thing
+ test = expression.isExpression("ADDRESSBOOK_OBJECT.ADDRESSBOOK_RESOURCE_ID", str(self.calendarid), True)
+ self.expression = test.andWith(self.expression)
+
# Generate ' where ...' partial statement
self.sout.write(self.WHERE)
self.generateExpression(self.expression)
@@ -1305,15 +1371,18 @@
-class postgresqladbkgenerator(FormatParamStyleMixin, oraclesqladbkgenerator):
+class postgresqladbkgenerator(FormatParamStyleMixin, CardDAVSQLBehaviorMixin, sqlgenerator):
"""
- Query generator for PostgreSQL indexed searches. Inherit 'real' database
- behavior from L{oracleadbkgenerator}, and %s-style formatting from
- L{FormatParamStyleMixin}.
+ Query generator for PostgreSQL indexed searches.
"""
+class oraclesqladbkgenerator(CardDAVSQLBehaviorMixin, sqlgenerator):
+ """
+ Query generator for Oracle indexed searches.
+ """
+
class PostgresLegacyABIndexEmulator(LegacyIndexHelper):
"""
Emulator for L{twistedcaldv.index.Index} and
@@ -1356,7 +1425,7 @@
def searchValid(self, filter):
- if isinstance(filter, carddavxml.Filter):
+ if isinstance(filter, addressbookqueryfilter.Filter):
qualifiers = addressbookquery.sqladdressbookquery(filter)
else:
qualifiers = None
@@ -1379,7 +1448,7 @@
else:
generator = postgresqladbkgenerator
# Make sure we have a proper Filter element and get the partial SQL statement to use.
- if isinstance(filter, carddavxml.Filter):
+ if isinstance(filter, addressbookqueryfilter.Filter):
qualifiers = addressbookquery.sqladdressbookquery(
filter, self.addressbook._resourceID, generator=generator)
else:
@@ -1392,10 +1461,10 @@
)
else:
rowiter = yield Select(
- [schema.ADDRESSBOOK_OBJECT.RESOURCE_NAME,
- schema.ADDRESSBOOK_OBJECT.VCARD_UID],
- From=schema.ADDRESSBOOK_OBJECT,
- Where=schema.ADDRESSBOOK_OBJECT.ADDRESSBOOK_RESOURCE_ID ==
+ [self._objectSchema.RESOURCE_NAME,
+ self._objectSchema.VCARD_UID],
+ From=self._objectSchema,
+ Where=self._objectSchema.ADDRESSBOOK_RESOURCE_ID ==
self.addressbook._resourceID
).on(self.addressbook._txn)
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/test_util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/test_util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/test_util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -26,9 +26,6 @@
from twisted.internet.protocol import Protocol
from twisted.trial.unittest import TestCase
-from twistedcaldav.config import config
-from twistedcaldav.memcacher import Memcacher
-
from txdav.caldav.datastore.test.common import CommonTests
from txdav.carddav.datastore.test.common import CommonTests as ABCommonTests
from txdav.common.datastore.file import CommonDataStore
@@ -48,9 +45,7 @@
Set up two stores to migrate between.
"""
# Add some files to the file store.
- self.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
- self.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
- self.patch(Memcacher, "allowTestCache", True)
+
self.filesPath = CachingFilePath(self.mktemp())
self.filesPath.createDirectory()
fileStore = self.fileStore = CommonDataStore(
Modified: CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/util.py 2011-06-30 16:48:43 UTC (rev 7696)
+++ CalendarServer/branches/users/cdaboo/timezones/txdav/common/datastore/test/util.py 2011-06-30 20:44:13 UTC (rev 7697)
@@ -20,6 +20,8 @@
"""
import gc
+from hashlib import md5
+from random import Random
from zope.interface.verify import verifyObject
from zope.interface.exceptions import BrokenMethodImplementation,\
DoesNotImplement
@@ -76,6 +78,7 @@
@return: a L{Deferred} which fires with an L{IDataStore}.
"""
+ disableMemcacheForTest(testCase)
dbRoot = CachingFilePath(self.SHARED_DB_PATH)
attachmentRoot = dbRoot.child("attachments")
if self.sharedService is None:
@@ -125,11 +128,12 @@
attachmentRoot.createDirectory()
except OSError:
pass
+ currentTestID = testCase.id()
cp = ConnectionPool(self.sharedService.produceConnection)
+ quota = deriveQuota(currentTestID)
store = CommonDataStore(
- cp.connection, notifierFactory, attachmentRoot
+ cp.connection, notifierFactory, attachmentRoot, quota=quota
)
- currentTestID = testCase.id()
store.label = currentTestID
cp.startService()
def stopIt():
@@ -182,6 +186,31 @@
+def deriveQuota(testID):
+ """
+ Derive a distinctive quota number for a specific test, based on its ID.
+ This generates a quota which is small enough that tests may trivially exceed
+ it if they wish to do so, but distinctive enough that it may be compared
+ without the risk of testing only a single value for quota.
+
+ Since SQL stores are generally built during test construction, it's awkward
+ to have tests which specifically construct a store to inspect quota-related
+ state; this allows us to have the test and the infrastructure agree on a
+ number.
+
+ @param testID: The identifier for a test, as returned by L{TestCase.id}.
+
+ @type testID: C{str}
+ """
+ h = md5(testID)
+ seed = int(h.hexdigest(), 16)
+ r = Random(seed)
+ baseline = 2000
+ fuzz = r.randint(1, 1000)
+ return baseline + fuzz
+
+
+
@inlineCallbacks
def populateCalendarsFrom(requirements, store):
"""
@@ -423,3 +452,24 @@
def reset(self):
self.history = []
+
+
+
+def disableMemcacheForTest(aTest):
+ """
+ Disable all memcache logic for the duration of a test; we shouldn't be
+ starting or connecting to any memcache stuff for most tests.
+ """
+
+ # These imports are local so that they don't accidentally leak to anything
+ # else in this module; nothing else in this module should ever touch global
+ # configuration. -glyph
+
+ from twistedcaldav.config import config
+ from twistedcaldav.memcacher import Memcacher
+
+ aTest.patch(config.Memcached.Pools.Default, "ClientEnabled", False)
+ aTest.patch(config.Memcached.Pools.Default, "ServerEnabled", False)
+ aTest.patch(Memcacher, "allowTestCache", True)
+
+
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110630/dfb703dc/attachment-0001.html>
More information about the calendarserver-changes
mailing list